From fcd30a11ba5b50d27919e39f1babfdd3224c96b9 Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Wed, 6 May 2020 17:38:05 +0200 Subject: [PATCH 3/3] lingo: handle empty payload in ReturnView (#42581) --- combo/apps/lingo/views.py | 23 +++++++++++++++++----- tests/test_lingo_payment.py | 39 +++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/combo/apps/lingo/views.py b/combo/apps/lingo/views.py index 00397da5..43812d58 100644 --- a/combo/apps/lingo/views.py +++ b/combo/apps/lingo/views.py @@ -554,8 +554,9 @@ class PaymentView(View): payment = get_eopayment_object(request, payment_backend) logger = logging.getLogger(__name__) logger.info(u'received payment response: %r', backend_response) + extra_info = kwargs.pop('payment_extra_info', {}) try: - payment_response = payment.response(backend_response) + payment_response = payment.response(backend_response, **extra_info) except eopayment.PaymentException as e: logger.error(u'failed to process payment response: %s', e, extra={'eopayment_raw_response': repr(backend_response)}) @@ -678,23 +679,36 @@ class ReturnView(PaymentView): return super(ReturnView, self).dispatch(*args, **kwargs) def get(self, request, *args, **kwargs): - if not request.environ['QUERY_STRING']: - return HttpResponseBadRequest('Missing query string') return self.handle_return(request, request.environ['QUERY_STRING'], **kwargs) def post(self, request, *args, **kwargs): return self.handle_return(request, force_text(request.body) or request.environ['QUERY_STRING'], **kwargs) def handle_return(self, request, backend_response, **kwargs): + payment_extra_info = {'redirect': True} + transaction = None transaction_id = kwargs.get('transaction_signature') if transaction_id: try: transaction_id = signing_loads(transaction_id) except signing.BadSignature: + transaction_id = None + + if transaction_id: + # retrieve info about previously known state + try: + current_transaction = Transaction.objects.get(pk=transaction_id) + except Transaction.DoesNotExist: pass + else: + payment_extra_info['order_id_hint'] = current_transaction.order_id + payment_extra_info['order_status_hint'] = current_transaction.status + try: - transaction = self.handle_response(request, backend_response, **kwargs) + transaction = self.handle_response( + request, backend_response, payment_extra_info=payment_extra_info, **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 @@ -702,7 +716,6 @@ class ReturnView(PaymentView): if transaction_id: return HttpResponseRedirect(get_payment_status_view(transaction_id)) return HttpResponseRedirect(get_basket_url()) - except PaymentException as e: messages.error(request, _('We are sorry but the payment service ' 'failed to provide a correct answer.')) diff --git a/tests/test_lingo_payment.py b/tests/test_lingo_payment.py index 738762c2..22488ca4 100644 --- a/tests/test_lingo_payment.py +++ b/tests/test_lingo_payment.py @@ -766,6 +766,45 @@ def test_payment_callback_no_regie(app, basket_page, regie, user, with_payment_b assert data['amount'] == '11.50' +@pytest.mark.parametrize('with_payment_backend', [False, True]) +def test_payment_return_without_query_string(app, basket_page, regie, user, with_payment_backend): + 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/') + resp = login(app).get(basket_page.get_online_url()) + resp = resp.form.submit() + assert resp.status_code == 302 + location = resp.location + parsed = urlparse.urlparse(location) + qs = urlparse.parse_qs(parsed.query) + return_url = qs['return_url'][0] + transaction_id = qs['transaction_id'][0] + data = {'transaction_id': transaction_id, 'signed': True, + 'amount': qs['amount'][0], 'ok': True} + + # payment status is obtained through callback + callback_url = get_url(with_payment_backend, 'lingo-callback', regie) + with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request: + get_resp = app.get(callback_url, params=data) + transaction = Transaction.objects.get(order_id=transaction_id) + assert transaction.status == 3 + + # then return view is called without any data, which should be expected by the backend + with mock.patch.object(eopayment.dummy.Payment, 'response', autospec=True) as mock_response: + mock_response.return_value = eopayment.common.PaymentResponse(result=transaction.status, + order_id=transaction.order_id) + get_resp = app.get(return_url) + mock_response.assert_called_once_with( + mock.ANY, '', redirect=True, order_id_hint=transaction.order_id, + order_status_hint=transaction.status + ) + assert get_resp.status_code == 302 + resp = app.get(get_resp['Location']) + assert 'Your payment has been succesfully registered.' in resp.text + + @pytest.mark.parametrize('with_payment_backend', [False, True]) def test_nonexisting_transaction(app, regie, user, with_payment_backend): app = login(app) -- 2.20.1