lingo: add validation/cancellation endpoints (#12766)
This commit is contained in:
parent
ab4a5cdb10
commit
1b6ac4c4b2
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import jsonfield.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('lingo', '0020_auto_20160606_1803'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TransactionOperation',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('kind', models.CharField(max_length=65, choices=[(b'validation', 'Validation'), (b'cancellation', 'Cancellation')])),
|
||||
('amount', models.DecimalField(max_digits=8, decimal_places=2)),
|
||||
('creation_date', models.DateTimeField(auto_now_add=True)),
|
||||
('bank_result', jsonfield.fields.JSONField(default=dict, blank=True)),
|
||||
('transaction', models.ForeignKey(to='lingo.Transaction')),
|
||||
],
|
||||
),
|
||||
]
|
|
@ -222,8 +222,9 @@ class BasketItem(models.Model):
|
|||
message = {'result': 'ok'}
|
||||
if status == 'paid':
|
||||
transaction = self.transaction_set.filter(status=eopayment.PAID)[0]
|
||||
message['transaction_id'] = transaction.id
|
||||
message['order_id'] = transaction.order_id
|
||||
message['transaction_id'] = transaction.bank_transaction_id
|
||||
message['bank_transaction_id'] = transaction.bank_transaction_id
|
||||
message['bank_data'] = transaction.bank_data
|
||||
headers = {'content-type': 'application/json'}
|
||||
r = requests.post(url, data=json.dumps(message), headers=headers, timeout=3)
|
||||
|
@ -301,6 +302,19 @@ class Transaction(models.Model):
|
|||
EXPIRED: _('Expired')
|
||||
}.get(self.status) or _('Unknown')
|
||||
|
||||
|
||||
class TransactionOperation(models.Model):
|
||||
OPERATIONS = [
|
||||
('validation', _('Validation')),
|
||||
('cancellation', _('Cancellation')),
|
||||
]
|
||||
transaction = models.ForeignKey(Transaction)
|
||||
kind = models.CharField(max_length=65, choices=OPERATIONS)
|
||||
amount = models.DecimalField(decimal_places=2, max_digits=8)
|
||||
creation_date = models.DateTimeField(auto_now_add=True)
|
||||
bank_result = JSONField(blank=True)
|
||||
|
||||
|
||||
@register_cell_class
|
||||
class LingoBasketCell(CellBase):
|
||||
|
||||
|
|
|
@ -20,7 +20,8 @@ from combo.urls_utils import decorated_includes, manager_required
|
|||
|
||||
from .views import (RegiesApiView, AddBasketItemApiView, PayView, CallbackView,
|
||||
ReturnView, ItemDownloadView, ItemView, CancelItemView,
|
||||
RemoveBasketItemApiView)
|
||||
RemoveBasketItemApiView, ValidateTransactionApiView,
|
||||
CancelTransactionApiView)
|
||||
from .manager_views import (RegieListView, RegieCreateView, RegieUpdateView,
|
||||
RegieDeleteView, TransactionListView, ManagerHomeView)
|
||||
|
||||
|
@ -41,6 +42,10 @@ urlpatterns = patterns('',
|
|||
name='api-add-basket-item'),
|
||||
url('^api/lingo/remove-basket-item$', RemoveBasketItemApiView.as_view(),
|
||||
name='api-remove-basket-item'),
|
||||
url('^api/lingo/validate-transaction$', ValidateTransactionApiView.as_view(),
|
||||
name='api-validate-transaction'),
|
||||
url('^api/lingo/cancel-transaction$', CancelTransactionApiView.as_view(),
|
||||
name='api-cancel-transaction'),
|
||||
url(r'^lingo/pay$', PayView.as_view(), name='lingo-pay'),
|
||||
url(r'^lingo/cancel/(?P<pk>\w+)/$', CancelItemView.as_view(), name='lingo-cancel-item'),
|
||||
url(r'^lingo/callback/(?P<regie_pk>\w+)/$', CallbackView.as_view(), name='lingo-callback'),
|
||||
|
|
|
@ -44,7 +44,8 @@ try:
|
|||
except ImportError:
|
||||
UserSAMLIdentifier = None
|
||||
|
||||
from .models import Regie, BasketItem, Transaction, LingoBasketCell
|
||||
from .models import (Regie, BasketItem, Transaction, TransactionOperation,
|
||||
LingoBasketCell)
|
||||
|
||||
def get_eopayment_object(request, regie):
|
||||
options = regie.service_options
|
||||
|
@ -186,6 +187,90 @@ class RemoveBasketItemApiView(View):
|
|||
return response
|
||||
|
||||
|
||||
class ValidateTransactionApiView(View):
|
||||
http_method_names = ['post', 'options']
|
||||
|
||||
@csrf_exempt
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super(ValidateTransactionApiView, self).dispatch(*args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
key = getattr(settings, 'LINGO_API_SIGN_KEY', '12345')
|
||||
if not check_query(request.META['QUERY_STRING'], key):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
try:
|
||||
transaction = Transaction.objects.get(id=request.GET['transaction_id'])
|
||||
except Transaction.DoesNotExist:
|
||||
logger.warning(u'received validate request for unknown transaction %s',
|
||||
request.GET['transaction_id'])
|
||||
raise Http404
|
||||
|
||||
payment = get_eopayment_object(request, transaction.regie)
|
||||
amount = request.GET['amount']
|
||||
|
||||
logger.info(u'validating amount %s for transaction %s', amount, smart_text(transaction.id))
|
||||
try:
|
||||
result = payment.backend.validate(Decimal(amount), transaction.bank_data)
|
||||
except eopayment.ResponseError as e:
|
||||
logger.error(u'failed in validation operation: %s', e)
|
||||
response = HttpResponse(content_type='application/json')
|
||||
response.write(json.dumps({'err': 1, 'e': unicode(e)}))
|
||||
return response
|
||||
|
||||
logger.info(u'bank validation result: %r', result)
|
||||
operation = TransactionOperation(transaction=transaction,
|
||||
kind='validation', amount=Decimal(amount), bank_result=result)
|
||||
operation.save()
|
||||
|
||||
response = HttpResponse(content_type='application/json')
|
||||
response.write(json.dumps({'err': 0, 'extra': result}))
|
||||
return response
|
||||
|
||||
|
||||
class CancelTransactionApiView(View):
|
||||
http_method_names = ['post', 'options']
|
||||
|
||||
@csrf_exempt
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super(CancelTransactionApiView, self).dispatch(*args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
key = getattr(settings, 'LINGO_API_SIGN_KEY', '12345')
|
||||
if not check_query(request.META['QUERY_STRING'], key):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
try:
|
||||
transaction = Transaction.objects.get(id=request.GET['transaction_id'])
|
||||
except Transaction.DoesNotExist:
|
||||
logger.warning(u'received validate request for unknown transaction %s',
|
||||
request.GET['transaction_id'])
|
||||
raise Http404
|
||||
|
||||
payment = get_eopayment_object(request, transaction.regie)
|
||||
amount = request.GET['amount']
|
||||
|
||||
logger.info(u'cancelling amount %s for transaction %s', amount, smart_text(transaction.id))
|
||||
try:
|
||||
result = payment.backend.cancel(Decimal(amount), transaction.bank_data)
|
||||
except eopayment.ResponseError as e:
|
||||
logger.error(u'failed in cancel operation: %s', e)
|
||||
response = HttpResponse(content_type='application/json')
|
||||
response.write(json.dumps({'err': 1, 'e': unicode(e)}))
|
||||
return response
|
||||
|
||||
logger.info(u'bank cancellation result: %r', result)
|
||||
operation = TransactionOperation(transaction=transaction,
|
||||
kind='cancellation', amount=Decimal(amount), bank_result=result)
|
||||
operation.save()
|
||||
|
||||
response = HttpResponse(content_type='application/json')
|
||||
response.write(json.dumps({'err': 0, 'extra': result}))
|
||||
return response
|
||||
|
||||
|
||||
class PayView(View):
|
||||
@atomic
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
|
|
@ -184,6 +184,8 @@ def ellipsize(text, length=50):
|
|||
|
||||
def check_query(query, key, known_nonce=None, timedelta=30):
|
||||
parsed = urlparse.parse_qs(query)
|
||||
if not 'signature' in parsed:
|
||||
return False
|
||||
signature = base64.b64decode(parsed['signature'][0])
|
||||
algo = parsed['algo'][0]
|
||||
timestamp = parsed['timestamp'][0]
|
||||
|
|
|
@ -5,6 +5,7 @@ import urlparse
|
|||
import urllib
|
||||
from decimal import Decimal
|
||||
import json
|
||||
import mock
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.urlresolvers import reverse
|
||||
|
@ -15,7 +16,8 @@ from webtest import TestApp
|
|||
|
||||
from django.test import Client
|
||||
|
||||
from combo.apps.lingo.models import Regie, BasketItem, Transaction, RemoteItem, EXPIRED
|
||||
from combo.apps.lingo.models import (Regie, BasketItem, Transaction,
|
||||
TransactionOperation, RemoteItem, EXPIRED)
|
||||
from combo.apps.lingo.management.commands.update_transactions import Command as UpdateTransactionsCommand
|
||||
from combo.utils import sign_url
|
||||
|
||||
|
@ -214,3 +216,63 @@ def test_transaction_expiration():
|
|||
|
||||
assert Transaction.objects.get(id=t1.id).status == EXPIRED
|
||||
assert Transaction.objects.get(id=t2.id).status == 0
|
||||
|
||||
def test_transaction_validate(regie, user):
|
||||
t1 = Transaction(regie=regie, bank_data={'bank': 'data'}, amount=12,
|
||||
status=eopayment.PAID)
|
||||
t1.save()
|
||||
|
||||
url = reverse('api-validate-transaction') + '?amount=10&transaction_id=0'
|
||||
resp = client.post(url, content_type='application/json')
|
||||
assert resp.status_code == 403
|
||||
|
||||
url = reverse('api-validate-transaction') + '?amount=10&transaction_id=0'
|
||||
url = sign_url(url, settings.LINGO_API_SIGN_KEY)
|
||||
resp = client.post(url, content_type='application/json')
|
||||
assert resp.status_code == 404
|
||||
|
||||
url = reverse('api-validate-transaction') + '?amount=10&transaction_id=%s' % t1.id
|
||||
url = sign_url(url, settings.LINGO_API_SIGN_KEY)
|
||||
resp = client.post(url, content_type='application/json')
|
||||
assert json.loads(resp.content)['err'] == 0
|
||||
operations = TransactionOperation.objects.filter(transaction=t1)
|
||||
assert len(operations) == 1
|
||||
assert operations[0].amount == 10
|
||||
|
||||
with mock.patch.object(eopayment.dummy.Payment, 'validate', autospec=True) as mock_validate:
|
||||
mock_validate.side_effect = eopayment.ResponseError
|
||||
url = reverse('api-validate-transaction') + '?amount=10&transaction_id=%s' % t1.id
|
||||
url = sign_url(url, settings.LINGO_API_SIGN_KEY)
|
||||
resp = client.post(url, content_type='application/json')
|
||||
assert json.loads(resp.content)['err'] == 1
|
||||
assert TransactionOperation.objects.filter(transaction=t1).count() == 1
|
||||
|
||||
def test_transaction_cancel(regie, user):
|
||||
t1 = Transaction(regie=regie, bank_data={'bank': 'data'}, amount=12,
|
||||
status=eopayment.PAID)
|
||||
t1.save()
|
||||
|
||||
url = reverse('api-cancel-transaction') + '?amount=10&transaction_id=0'
|
||||
resp = client.post(url, content_type='application/json')
|
||||
assert resp.status_code == 403
|
||||
|
||||
url = reverse('api-cancel-transaction') + '?amount=10&transaction_id=0'
|
||||
url = sign_url(url, settings.LINGO_API_SIGN_KEY)
|
||||
resp = client.post(url, content_type='application/json')
|
||||
assert resp.status_code == 404
|
||||
|
||||
url = reverse('api-cancel-transaction') + '?amount=10&transaction_id=%s' % t1.id
|
||||
url = sign_url(url, settings.LINGO_API_SIGN_KEY)
|
||||
resp = client.post(url, content_type='application/json')
|
||||
assert json.loads(resp.content)['err'] == 0
|
||||
operations = TransactionOperation.objects.filter(transaction=t1)
|
||||
assert len(operations) == 1
|
||||
assert operations[0].amount == 10
|
||||
|
||||
with mock.patch.object(eopayment.dummy.Payment, 'cancel', autospec=True) as mock_cancel:
|
||||
mock_cancel.side_effect = eopayment.ResponseError
|
||||
url = reverse('api-cancel-transaction') + '?amount=10&transaction_id=%s' % t1.id
|
||||
url = sign_url(url, settings.LINGO_API_SIGN_KEY)
|
||||
resp = client.post(url, content_type='application/json')
|
||||
assert json.loads(resp.content)['err'] == 1
|
||||
assert TransactionOperation.objects.filter(transaction=t1).count() == 1
|
||||
|
|
Loading…
Reference in New Issue