lingo: handle empty payload in ReturnView (#42581)

This commit is contained in:
Valentin Deniaud 2020-05-06 17:38:05 +02:00
parent ea0657f565
commit bda15dbcef
2 changed files with 57 additions and 5 deletions

View File

@ -562,8 +562,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)})
@ -686,23 +687,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
@ -710,7 +724,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.'))

View File

@ -825,6 +825,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)