lingo: handled signed responses on the return URL (#19709)

This commit is contained in:
Frédéric Péters 2018-02-19 13:54:48 +01:00
parent 2abe10a09b
commit f860a552c2
2 changed files with 99 additions and 25 deletions

View File

@ -383,8 +383,20 @@ class PayView(View):
raise NotImplementedError()
class CallbackView(View):
def handle_callback(self, request, backend_response, **kwargs):
class PaymentException(Exception):
pass
class UnsignedPaymentException(PaymentException):
pass
class UnknownPaymentException(PaymentException):
pass
class PaymentView(View):
def handle_response(self, request, backend_response, **kwargs):
regie = Regie.objects.get(id=kwargs.get('regie_pk'))
payment = get_eopayment_object(request, regie)
logger = logging.getLogger(__name__)
@ -393,7 +405,8 @@ class CallbackView(View):
except eopayment.ResponseError as e:
logger.error(u'failed to process payment response: %s', e,
extra={'eopayment_raw_response': repr(backend_response)})
return HttpResponseBadRequest()
raise PaymentException('Failed to process payment response')
extra_info = {
'eopayment_order_id': smart_text(payment_response.order_id),
'eopayment_response': repr(payment_response),
@ -405,14 +418,14 @@ class CallbackView(View):
# that :/
logger.warning(u'received unsigned payment response with id %s',
smart_text(payment_response.order_id), extra=extra_info)
return HttpResponseBadRequest('Unsigned payment response')
raise UnsignedPaymentException('Received unsigned payment response')
try:
transaction = Transaction.objects.get(order_id=payment_response.order_id)
except Transaction.DoesNotExist:
logger.warning(u'received unknown payment response with id %s',
smart_text(payment_response.order_id), extra=extra_info)
raise Http404
raise UnknownPaymentException('Received unknown payment response')
else:
extra_info['lingo_transaction_id'] = transaction.pk
if transaction.user:
@ -426,29 +439,31 @@ class CallbackView(View):
'(status: %s, new status: %s)' % (
transaction.status, payment_response.result))
# check if transaction belongs to right regie
if not transaction.regie == regie:
logger.warning(u'received payment for inappropriate regie '
'(expecteds: %s, received: %s)' % (transaction.regie, regie))
raise PaymentException('Invalid payment regie')
transaction.status = payment_response.result
transaction.bank_transaction_id = payment_response.transaction_id
transaction.bank_data = payment_response.bank_data
transaction.end_date = timezone.now()
transaction.save()
# check if transaction belongs to right regie
if not transaction.regie == regie:
return HttpResponseBadRequest('Invalid payment regie')
if payment_response.result == eopayment.WAITING:
# mark basket items as waiting for payment confirmation
transaction.items.all().update(waiting_date=timezone.now())
return HttpResponse()
return transaction
if payment_response.result == eopayment.CANCELLED:
# mark basket items as no longer waiting so the user can restart a
# payment.
transaction.items.all().update(waiting_date=None)
return HttpResponse()
return transaction
if payment_response.result not in (eopayment.PAID, eopayment.ACCEPTED):
return HttpResponse()
return transaction
transaction.items.update(payment_date=transaction.end_date)
@ -461,6 +476,18 @@ class CallbackView(View):
regie.compute_extra_fees(user=transaction.user)
if transaction.remote_items:
transaction.first_notify_remote_items_of_payments()
return transaction
class CallbackView(PaymentView):
def handle_callback(self, request, backend_response, **kwargs):
try:
self.handle_response(request, backend_response, **kwargs)
except UnknownPaymentException as e:
raise Http404(unicode(e))
except PaymentException as e:
return HttpResponseBadRequest(unicode(e))
return HttpResponse()
def get(self, request, *args, **kwargs):
@ -474,7 +501,7 @@ class CallbackView(View):
return super(CallbackView, self).dispatch(*args, **kwargs)
class ReturnView(View):
class ReturnView(PaymentView):
@csrf_exempt
def dispatch(self, *args, **kwargs):
return super(ReturnView, self).dispatch(*args, **kwargs)
@ -486,24 +513,23 @@ class ReturnView(View):
return self.handle_return(request, request.body, **kwargs)
def handle_return(self, request, backend_response, **kwargs):
regie = Regie.objects.get(id=kwargs.get('regie_pk'))
payment = get_eopayment_object(request, regie)
transaction = None
try:
payment_response = payment.response(backend_response)
except eopayment.ResponseError as e:
# if eopayment can't get response from query string redirect to
# homepage
logging.error('failed to process payment response (%r)', e)
transaction = self.handle_response(request, backend_response, **kwargs)
except UnsignedPaymentException as e:
# some payment backends do not sign return URLs, don't mark this as
# an error, they will provide a notification to the callback
# endpoint.
pass
except PaymentException as e:
messages.error(request, _('We are sorry but the payment service '
'failed to provide a correct answer.'))
return HttpResponseRedirect(get_basket_url())
if payment_response.result in (eopayment.PAID, eopayment.ACCEPTED):
messages.info(request, regie.get_text_on_success())
if transaction and transaction.status in (eopayment.PAID, eopayment.ACCEPTED):
messages.info(request, transaction.regie.get_text_on_success())
transaction = Transaction.objects.get(order_id=payment_response.order_id)
if request.session.get('lingo_next_url'):
if transaction and request.session.get('lingo_next_url'):
redirect_url = request.session['lingo_next_url'].get(transaction.order_id)
if redirect_url:
return HttpResponseRedirect(redirect_url)

View File

@ -351,6 +351,8 @@ def test_cancel_basket_item_from_cell(key, regie, user):
assert resp.status_code == 404
def test_payment_callback(regie, user):
page = Page(title='xxx', slug='index', template_name='standard')
page.save()
item = BasketItem.objects.create(user=user, regie=regie,
subject='test_item', amount='10.5',
source_url='http://example.org/testitem/')
@ -397,6 +399,8 @@ def test_payment_callback(regie, user):
# call return view
get_resp = client.get(reverse('lingo-return', kwargs={'regie_pk': regie.pk}), data)
assert get_resp.status_code == 302
resp = client.get(get_resp['Location'])
assert 'Your payment has been succesfully registered.' in resp.content
def test_payment_callback_no_regie(regie, user):
item = BasketItem.objects.create(user=user, regie=regie,
@ -482,6 +486,50 @@ def test_payment_callback_waiting(regie, user):
assert BasketItem.objects.get(id=item.id).payment_date
assert BasketItem.get_items_to_be_paid(user).count() == 0
def test_payment_no_callback_just_return(regie, user):
page = Page(title='xxx', slug='test_basket_cell', template_name='standard')
page.save()
cell = LingoBasketCell(page=page, placeholder='content', order=0)
cell.save()
item = BasketItem.objects.create(user=user, regie=regie,
subject='test_item', amount='10.5',
source_url='http://example.org/testitem/')
login()
resp = client.post(reverse('lingo-pay'), {'regie': regie.pk})
assert resp.status_code == 302
location = resp.get('location')
parsed = urlparse.urlparse(location)
qs = urlparse.parse_qs(parsed.query)
transaction_id = qs['transaction_id'][0]
data = {'transaction_id': transaction_id,
'amount': qs['amount'][0], 'ok': True}
assert data['amount'] == '10.50'
# call return with unsigned POST
return_url = reverse('lingo-return', kwargs={'regie_pk': regie.id})
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
get_resp = client.post(return_url, urllib.urlencode(data),
content_type='application/x-www-form-urlencoded')
assert request.call_count == 0
assert get_resp.status_code == 302
assert urlparse.urlparse(get_resp['location']).path == '/test_basket_cell/'
assert Transaction.objects.get(order_id=transaction_id).status == 0 # not paid
# call return with signed POST
data['signed'] = True
return_url = reverse('lingo-return', kwargs={'regie_pk': regie.id})
with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request:
get_resp = client.post(return_url, urllib.urlencode(data),
content_type='application/x-www-form-urlencoded')
url = request.call_args[0][1]
assert url.startswith('http://example.org/testitem/jump/trigger/paid')
assert get_resp.status_code == 302
assert urlparse.urlparse(get_resp['location']).path == '/'
resp = client.get(get_resp['Location'])
assert 'Your payment has been succesfully registered.' in resp.content
assert Transaction.objects.get(order_id=transaction_id).status == eopayment.PAID
def test_transaction_expiration():
t1 = Transaction(status=0)
t1.save()