Add coingate

master
Alice 6 years ago
parent 98e8a508b6
commit c90827878e

@ -228,6 +228,13 @@ PAYMENTS_BACKENDS = {
'URL': 'http://test:test@127.0.0.1:18332/',
'BITCOIN_VALUE': 36000, # Value of one bitcoin in currency*100
},
'_coingate': {
# 'SANDBOX': True,
# 'API_BASE': '',
'API_TOKEN': '',
'CURRENCY': '',
'TITLE': '',
}
}
PAYMENTS_CURRENCY = ('eur', '')

@ -51,7 +51,7 @@ class VPNUser(models.Model):
@property
def time_left(self):
return timezone.now() - self.expiration
return self.expiration - timezone.now()
def add_paid_time(self, time):
now = timezone.now()
@ -63,6 +63,17 @@ class VPNUser(models.Model):
if core.VPN_AUTH_STORAGE == 'core':
core.update_user_expiration(self.user)
def remove_paid_time(self, time):
now = timezone.now()
if self.expiration < now:
return
self.expiration -= time
if core.VPN_AUTH_STORAGE == 'core':
core.update_user_expiration(self.user)
def give_trial_period(self):
self.add_paid_time(get_trial_period_duration())
self.trial_periods_given += 1

@ -5,4 +5,5 @@ from .paypal import PaypalBackend
from .bitcoin import BitcoinBackend
from .stripe import StripeBackend
from .coinbase import CoinbaseBackend
from .coingate import CoinGateBackend

@ -0,0 +1,131 @@
import string
import random
import requests
import logging
from django.shortcuts import redirect
from django.utils.translation import ugettext_lazy as _
from django.urls import reverse
from django.conf import settings as project_settings
from .base import BackendBase
logger = logging.getLogger(__name__)
prng = random.SystemRandom()
def generate_token(length=16):
charset = string.digits + string.ascii_letters
return ''.join([prng.choice(charset) for _ in range(length)])
class CoinGateBackend(BackendBase):
backend_id = 'coingate'
backend_verbose_name = _("CoinGate")
backend_display_name = _("Cryptocurrencies")
backend_has_recurring = False
def __init__(self, settings):
self.api_token = settings.get('API_TOKEN')
if not self.api_token:
return
self.currency = settings.get('CURRENCY', 'EUR')
self.title = settings.get('TITLE', 'VPN Payment')
if settings.get('SANDBOX'):
self.api_base = "https://api-sandbox.coingate.com"
else:
default_base = "https://api.coingate.com"
self.api_base = settings.get('API_BASE', default_base)
self.backend_enabled = True
def _post(self, endpoint, **kwargs):
headers = {
'Authorization': 'Token ' + self.api_token,
}
url = self.api_base + endpoint
response = requests.post(url, headers=headers, **kwargs)
response.raise_for_status()
j = response.json()
return j
def new_payment(self, payment):
root_url = project_settings.ROOT_URL
assert root_url
token = generate_token()
order = self._post('/v2/orders', data={
'order_id': str(payment.id),
'price_amount': payment.amount / 100,
'price_currency': self.currency,
'receive_currency': self.currency,
'title': self.title,
'callback_url': root_url + reverse('payments:cb_coingate', args=(payment.id,)),
'cancel_url': root_url + reverse('payments:cancel', args=(payment.id,)),
'success_url': root_url + reverse('payments:view', args=(payment.id,)),
'token': token,
})
url = order['payment_url']
payment.backend_data['coingate_id'] = order['id']
payment.backend_data['coingate_url'] = url
payment.backend_data['coingate_token'] = token
payment.save()
return redirect(url)
def callback(self, payment, request):
post_data = request.POST
# Verify callback authenticity
saved_token = payment.backend_data.get('coingate_token')
if not saved_token:
logger.debug("payment does not have a coingate_token")
return False
token = post_data.get('token')
if token != saved_token:
logger.debug("unexpected token (%r != %r)", token, saved_token)
return False
order_id = post_data.get('order_id')
if order_id != str(payment.id):
logger.debug("unexpected order_id (%r != %r)", order_id, str(payment.id))
return False
# Handle event
status = post_data.get('status')
if status == 'new' or status == 'pending':
payment.update_status('new')
elif status == 'confirming':
payment.update_status('new', _("Confirming transaction"))
elif status == 'paid':
if payment.status in {'new', 'cancelled', 'error'}:
# we don't have the exact converted amount, but it's not
# important. settings to the requested amount for consistency
payment.paid_amount = payment.amount
payment.confirm()
elif status == 'invalid' or status == 'expired':
if payment.status != 'confirmed':
payment.update_status('cancelled')
elif status == 'refunded':
if payment.status == 'confirmed':
payment.refund()
else:
logger.debug("unexpected payment status: %r", status)
return False
payment.save()
return True
def get_ext_url(self, payment):
if not payment.backend_extid:
return None
return 'https://dashboard.stripe.com/payments/%s' % payment.backend_extid

@ -126,6 +126,22 @@ class Payment(models.Model):
def is_confirmed(self):
return self.status == 'confirmed'
def confirm(self):
self.user.vpnuser.add_paid_time(self.time)
self.user.vpnuser.on_payment_confirmed(self)
self.user.vpnuser.save()
self.update_status('confirmed')
def refund(self):
self.user.vpnuser.remove_paid_time(self.time)
self.user.vpnuser.save()
self.update_status('refunded')
def update_status(self, status, message=None):
assert any(c[0] == status for c in STATUS_CHOICES)
self.status = status
self.status_message = message
class Meta:
ordering = ('-created', )

@ -3,4 +3,5 @@
from .bitcoin import *
from .paypal import *
from .stripe import *
from .coingate import *

@ -0,0 +1,75 @@
from datetime import timedelta
from django.test import TestCase, RequestFactory
from django.http import HttpResponseRedirect
from django.contrib.auth.models import User
from payments.models import Payment
from payments.backends import CoinGateBackend
class CoinGateBackendTest(TestCase):
def setUp(self):
self.user = User.objects.create_user('test', 'test_user@example.com', None)
self.backend_settings = dict(
API_TOKEN='test',
TITLE='Test Title',
CURRENCY='EUR',
)
def test_payment(self):
payment = Payment.objects.create(
user=self.user,
time=timedelta(days=30),
backend_id='coingate',
amount=300
)
def fake_post(_backend, *, data={}):
self.assertEqual(data['order_id'], '1')
self.assertEqual(data['price_amount'], 3.0)
self.assertEqual(data['price_currency'], 'EUR')
self.assertEqual(data['receive_currency'], 'EUR')
return {'id': 42, 'payment_url': 'http://testtoken/'}
with self.settings(ROOT_URL='root'):
backend = CoinGateBackend(self.backend_settings)
backend._post = fake_post
redirect = backend.new_payment(payment)
self.assertIsInstance(redirect, HttpResponseRedirect)
self.assertEqual(redirect.url, 'http://testtoken/')
self.assertEqual(payment.backend_data.get('coingate_id'), 42)
# Test a standard successful payment callback flow
def post_callback(status):
callback_data = {
'token': payment.backend_data['coingate_token'],
'order_id': str(payment.id),
'status': status,
}
ipn_url = '/payments/callback/coingate/%d' % payment.id
ipn_request = RequestFactory().post(
ipn_url,
data=callback_data)
return backend.callback(payment, ipn_request)
r = post_callback('pending')
self.assertTrue(r)
self.assertEqual(payment.status, 'new')
r = post_callback('confirming')
self.assertTrue(r)
self.assertEqual(payment.status, 'new')
r = post_callback('paid')
self.assertTrue(r)
self.assertEqual(payment.status, 'confirmed')
self.assertEqual(payment.paid_amount, 300)
time_left_s = self.user.vpnuser.time_left.total_seconds()
self.assertAlmostEqual(time_left_s, payment.time.total_seconds(), delta=60)

@ -11,6 +11,7 @@ urlpatterns = [
url(r'^return_subscr/(?P<id>[0-9]+)$', views.return_subscr, name='return_subscr'),
url(r'^callback/paypal/(?P<id>[0-9]+)$', views.callback_paypal, name='cb_paypal'),
url(r'^callback/coingate/(?P<id>[0-9]+)$', views.callback_coingate, name='cb_coingate'),
url(r'^callback/stripe/(?P<id>[0-9]+)$', views.callback_stripe, name='cb_stripe'),
url(r'^callback/coinbase/$', views.callback_coinbase, name='cb_coinbase'),
url(r'^callback/paypal_subscr/(?P<id>[0-9]+)$', views.callback_paypal_subscr, name='cb_paypal_subscr'),

@ -90,6 +90,19 @@ def callback_stripe(request, id):
return redirect(reverse('payments:view', args=(id,)))
@csrf_exempt
def callback_coingate(request, id):
""" CoinGate payment callback """
if not BACKENDS['coingate'].backend_enabled:
return HttpResponseNotFound()
p = Payment.objects.get(id=id)
if BACKENDS['coingate'].callback(p, request):
return HttpResponse()
else:
return HttpResponseBadRequest()
@csrf_exempt
def callback_coinbase(request):
if not BACKENDS['coinbase'].backend_enabled:

@ -387,7 +387,7 @@ ul.errorlist {
text-align: center;
border: 1px solid #ccc;
border-radius: 5px;
margin: 0 1em;
margin: 0.5em 1em;
}
.install-guides li a {
line-height: 3em;

Loading…
Cancel
Save