From 60915c7d6d229be8dd4d4fc43481eb300ffea2c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Wed, 31 Jan 2018 15:25:13 +0100 Subject: [PATCH] lingo: add support for payment services returning 'WAITING' as status (#19362) --- .../0031_basketitem_waiting_date.py | 20 ++++++++++ combo/apps/lingo/models.py | 2 + combo/apps/lingo/views.py | 11 ++++++ tests/test_lingo_payment.py | 37 +++++++++++++++++++ 4 files changed, 70 insertions(+) create mode 100644 combo/apps/lingo/migrations/0031_basketitem_waiting_date.py diff --git a/combo/apps/lingo/migrations/0031_basketitem_waiting_date.py b/combo/apps/lingo/migrations/0031_basketitem_waiting_date.py new file mode 100644 index 00000000..f9573aed --- /dev/null +++ b/combo/apps/lingo/migrations/0031_basketitem_waiting_date.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.8 on 2018-01-31 14:24 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('lingo', '0030_transaction_to_be_paid_remote_items'), + ] + + operations = [ + migrations.AddField( + model_name='basketitem', + name='waiting_date', + field=models.DateTimeField(null=True), + ), + ] diff --git a/combo/apps/lingo/models.py b/combo/apps/lingo/models.py index 14159c53..b68cb117 100644 --- a/combo/apps/lingo/models.py +++ b/combo/apps/lingo/models.py @@ -217,6 +217,7 @@ class BasketItem(models.Model): user_cancellable = models.BooleanField(default=True) creation_date = models.DateTimeField(auto_now_add=True) cancellation_date = models.DateTimeField(null=True) + waiting_date = models.DateTimeField(null=True) payment_date = models.DateTimeField(null=True) notification_date = models.DateTimeField(null=True) @@ -228,6 +229,7 @@ class BasketItem(models.Model): return cls.objects.filter( user=user, payment_date__isnull=True, + waiting_date__isnull=True, cancellation_date__isnull=True) def notify(self, status): diff --git a/combo/apps/lingo/views.py b/combo/apps/lingo/views.py index b90fbb7b..c9d36a04 100644 --- a/combo/apps/lingo/views.py +++ b/combo/apps/lingo/views.py @@ -436,6 +436,17 @@ class CallbackView(View): 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() + + 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() + if payment_response.result not in (eopayment.PAID, eopayment.ACCEPTED): return HttpResponse() diff --git a/tests/test_lingo_payment.py b/tests/test_lingo_payment.py index 0acca835..27927d48 100644 --- a/tests/test_lingo_payment.py +++ b/tests/test_lingo_payment.py @@ -445,6 +445,43 @@ def test_nonexisting_transaction(regie, user): get_resp = client.get(callback_url, data) assert get_resp.status_code == 404 +def test_payment_callback_waiting(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], 'waiting': True} + assert data['amount'] == '10.50' + + # callback with WAITING state + callback_url = reverse('lingo-callback', kwargs={'regie_pk': regie.id}) + resp = client.get(callback_url, data) + assert resp.status_code == 200 + assert Transaction.objects.get(order_id=transaction_id).status == eopayment.WAITING + assert BasketItem.objects.get(id=item.id).waiting_date + assert not BasketItem.objects.get(id=item.id).payment_date + assert BasketItem.get_items_to_be_paid(user).count() == 0 + + # callback with PAID state + data = {'transaction_id': transaction_id, 'signed': True, + 'amount': qs['amount'][0], 'ok': True} + with mock.patch('combo.utils.requests_wrapper.RequestsSession.request') as request: + resp = client.get(callback_url, data) + assert resp.status_code == 200 + url = request.call_args[0][1] + assert url.startswith('http://example.org/testitem/jump/trigger/paid') + + assert Transaction.objects.get(order_id=transaction_id).status == eopayment.PAID + assert BasketItem.objects.get(id=item.id).payment_date + assert BasketItem.get_items_to_be_paid(user).count() == 0 + def test_transaction_expiration(): t1 = Transaction(status=0) t1.save()