From f6ad78a8b4b003055763acb17653d3468464c6f2 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Tue, 7 Apr 2020 15:04:38 +0200 Subject: [PATCH] dpark: accept UTC datetime in payment notification (#41373) --- passerelle/contrib/dpark/models.py | 31 ++++++++++++++++++++++++++++-- setup.py | 1 + tests/test_dpark.py | 26 +++++++++++++++++-------- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/passerelle/contrib/dpark/models.py b/passerelle/contrib/dpark/models.py index d34c36eb..a6c735b4 100644 --- a/passerelle/contrib/dpark/models.py +++ b/passerelle/contrib/dpark/models.py @@ -16,8 +16,11 @@ from __future__ import unicode_literals +import datetime import base64 +import pytz + from django.conf import settings from django.db import models from django.utils import six, timezone @@ -115,11 +118,31 @@ def date_to_isoformat(idate): if not idate: return None try: - return timezone.datetime.strptime(idate, '%Y%m%d').date().isoformat() + return datetime.datetime.strptime(idate, '%Y%m%d').date().isoformat() except (ValueError,): return idate +def date_or_datetime_to_local_date(value): + try: + dt = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S') + except ValueError: + pass + else: + dt = pytz.utc.localize(dt) + dt = dt.astimezone(pytz.timezone('Europe/Paris')) + return dt.date() + + try: + dt = datetime.datetime.strptime(value, '%Y%m%d') + except ValueError: + pass + else: + return dt.date() + + return None + + def normalize_reply(reply): excluded = ('CodeRetour', 'MessageRetour') serialized_reply = serialize_object(reply) @@ -414,6 +437,10 @@ class DPark(BaseResource): 'transaction_datetime', 'total_amount', 'application_external_id') ) + # We accept a simple date or a datetime using UTC, we convert it to Europe/Paris timezone on exit + transaction_date = date_or_datetime_to_local_date(data['transaction_datetime']) + if transaction_date is None: + raise APIError(_('Invalid value for transaction datetime')) pairings = Pairing.objects.filter(resource=self, nameid=data['nameid'], filenumber=data['filenumber']) @@ -427,7 +454,7 @@ class DPark(BaseResource): data['application_id'], data.get('applicaiton_payment_type', 10), total_amount, - data['transaction_datetime'], + transaction_date.strftime('%Y%m%d'), data['transaction_id']) for pairing in pairings: pairing.clear_cache() diff --git a/setup.py b/setup.py index 2a91f6b0..c8f72e7f 100755 --- a/setup.py +++ b/setup.py @@ -110,6 +110,7 @@ setup(name='passerelle', 'pdfrw', 'httplib2', 'xmlschema', + 'pytz', ], cmdclass={ 'build': build, diff --git a/tests/test_dpark.py b/tests/test_dpark.py index 4ac7ef28..58d2150e 100644 --- a/tests/test_dpark.py +++ b/tests/test_dpark.py @@ -73,7 +73,6 @@ def dpark(db): class ReplyDataClass(dict): - def __init__(self, **kwargs): self.__dict__.update(kwargs) super(ReplyDataClass, self).__init__(**kwargs) @@ -110,11 +109,12 @@ class MockedService(object): def get_client(success=True, error_class=None, replydata=None): + service = MockedService(success, error_class, replydata) def create_service(binging, operation_endpoint): - return MockedService(success, error_class, replydata) + return service - return mock.Mock(create_service=create_service) + return mock.Mock(create_service=create_service, service=service) def test_call_service_error(dpark, app): @@ -492,13 +492,22 @@ def test_get_payment_infos(dpark, app): assert data['numerodemande'] == '55555' -def test_payment_notification(dpark, app): - with mock.patch('passerelle.contrib.dpark.models.get_client') as client: +@pytest.mark.parametrize('transaction_datetime,expected_date', [ + ('20180611', '20180611'), + # UTC datetime should be converted to Europe/Paris date + ('2018-06-11T23:59:00', '20180612') +]) +def test_payment_notification(dpark, app, transaction_datetime, expected_date): + operation = mock.Mock(name='PLS_NOTIFCB') + service = mock.Mock(spec=['PLS_NOTIFCB'], PLS_NOTIFCB=operation) + create_service = mock.Mock(spec=[], return_value=service) + client = mock.NonCallableMock(spec=['create_service'], create_service=create_service) + with mock.patch('passerelle.contrib.dpark.models.get_client', return_value=client): nameid = 'abcd' * 8 filenumber = '1' * 9 params = { 'nameid': nameid, 'filenumber': filenumber, 'transaction_id': 'I123456789', - 'transaction_datetime': '2018-06-11T10:23', 'total_amount': '125', + 'transaction_datetime': transaction_datetime, 'total_amount': '125', 'application_id': '61718', 'application_external_id': 'E-8-N5UTAK6P' } url = '/dpark/test/notify-payment/' @@ -509,11 +518,12 @@ def test_payment_notification(dpark, app): 'nameid': nameid, 'firstnames': 'spam eggs', 'lastname': 'bar', 'filenumber': filenumber, 'badgenumber': '2' * 9} ) - client.return_value = get_client(replydata={'CodeRetour': '02', 'MessageRetour': u'Dossier inconnu'}) + operation.return_value = mock.Mock(CodeRetour='02', MessageRetour=u'Dossier inconnu') resp = app.post_json(url, params=params) + assert operation.call_args_list[-1].args[5] == expected_date assert resp.json['err'] == 1 assert resp.json['err_desc'] == 'Dossier inconnu' - client.return_value = get_client(replydata={'CodeRetour': '01'}) + operation.return_value = mock.Mock(CodeRetour='01') resp = app.post_json(url, params=params) assert resp.json['data'] is True -- 2.24.0