lingo: handled signed responses on the return URL (#19709)
This commit is contained in:
parent
2abe10a09b
commit
f860a552c2
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue