From b145464ab83ef6718c3faef526a81ae7f96a54a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Thu, 17 Aug 2017 09:19:45 +0200 Subject: [PATCH] lingo: add support for async payment notification in case of errors (#6638) --- .../lingo/management/commands/notify_payments.py | 11 +++-- combo/apps/lingo/views.py | 6 +-- tests/test_lingo_payment.py | 53 ++++++++++++++++++++++ 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/combo/apps/lingo/management/commands/notify_payments.py b/combo/apps/lingo/management/commands/notify_payments.py index d745e42..8a543ad 100644 --- a/combo/apps/lingo/management/commands/notify_payments.py +++ b/combo/apps/lingo/management/commands/notify_payments.py @@ -17,8 +17,10 @@ # along with this program. If not, see . import logging +import datetime from django.core.management.base import BaseCommand +from django.utils import timezone from combo.apps.lingo.models import BasketItem @@ -27,9 +29,12 @@ class Command(BaseCommand): def handle(self, *args, **kwargs): logger = logging.getLogger(__name__) - for item in BasketItem.objects.filter(payment_date__isnull=False, - notification_date__isnull=True): + now = timezone.now() + for item in BasketItem.objects.filter( + notification_date__isnull=True, + cancellation_date__isnull=True, + payment_date__lt=now-datetime.timedelta(minutes=5)): try: item.notify_payment() except: - logger.exception('error notifying payment for basket item %s', item.id) + logger.exception('error in async notification for basket item %s', item.id) diff --git a/combo/apps/lingo/views.py b/combo/apps/lingo/views.py index 9edd40c..5b25545 100644 --- a/combo/apps/lingo/views.py +++ b/combo/apps/lingo/views.py @@ -436,9 +436,9 @@ class CallbackView(View): item.save() try: item.notify_payment() - except RuntimeError: - # ignore errors, it should be retried later on if it fails - pass + except: + # ignore errors, it will be retried later on if it fails + logger.exception('error in sync notification for basket item %s', item.id) regie.compute_extra_fees(user=transaction.user) if transaction.remote_items: transaction.first_notify_remote_items_of_payments() diff --git a/tests/test_lingo_payment.py b/tests/test_lingo_payment.py index fee5815..52bbbc4 100644 --- a/tests/test_lingo_payment.py +++ b/tests/test_lingo_payment.py @@ -21,6 +21,7 @@ from combo.data.models import Page from combo.apps.lingo.models import (Regie, BasketItem, Transaction, TransactionOperation, RemoteItem, EXPIRED, LingoBasketCell) from combo.apps.lingo.management.commands.update_transactions import Command as UpdateTransactionsCommand +from combo.apps.lingo.management.commands.notify_payments import Command as NotifyPaymentsCommand from combo.utils import sign_url pytestmark = pytest.mark.django_db @@ -573,3 +574,55 @@ def test_extra_fees(key, regie, user): resp = client.get(callback_url, data) assert resp.status_code == 200 assert Transaction.objects.get(order_id=transaction_id).status == 3 + +def test_payment_callback_error(regie, user): + 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, 'signed': True, + 'amount': qs['amount'][0], 'ok': True} + assert data['amount'] == '10.50' + + # call callback with GET + callback_url = reverse('lingo-callback', kwargs={'regie_pk': regie.id}) + with mock.patch('combo.utils.RequestsSession.request') as request: + mock_response = mock.Mock() + def kaboom(): + raise Exception('kaboom') + mock_response.status_code = 500 + mock_response.raise_for_status = kaboom + request.return_value = mock_response + get_resp = client.get(callback_url, data) + url = request.call_args[0][1] + assert url.startswith('http://example.org/testitem/jump/trigger/paid') + assert get_resp.status_code == 200 + assert Transaction.objects.get(order_id=transaction_id).status == 3 + assert BasketItem.objects.get(id=item.id).payment_date + assert not BasketItem.objects.get(id=item.id).notification_date + + # too soon + NotifyPaymentsCommand().handle() + assert BasketItem.objects.get(id=item.id).payment_date + assert not BasketItem.objects.get(id=item.id).notification_date + + # fake delay + basket_item = BasketItem.objects.get(id=item.id) + basket_item.payment_date = timezone.now() - timedelta(hours=1) + basket_item.save() + + with mock.patch('combo.utils.RequestsSession.request') as request: + mock_response = mock.Mock() + mock_response.status_code = 200 + request.return_value = mock_response + NotifyPaymentsCommand().handle() + url = request.call_args[0][1] + assert url.startswith('http://example.org/testitem/jump/trigger/paid') + assert BasketItem.objects.get(id=item.id).payment_date + assert BasketItem.objects.get(id=item.id).notification_date -- 2.14.1