From 7ac96b4f1e407c8a3324269d46ccc064b98ccbe4 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Thu, 31 Jan 2019 16:57:02 +0100 Subject: [PATCH] tipi: implement automatic generation of REFDET (fixes #30272) --- eopayment/tipi.py | 53 ++++++++++++++++++++++++----------------- tests/test_tipi.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 21 deletions(-) diff --git a/eopayment/tipi.py b/eopayment/tipi.py index c53101e..015e939 100644 --- a/eopayment/tipi.py +++ b/eopayment/tipi.py @@ -1,4 +1,7 @@ +import re +import random from decimal import Decimal, ROUND_DOWN + from .common import (PaymentCommon, PaymentResponse, URL, PAID, DENIED, CANCELLED, ERROR, ResponseError) from six.moves.urllib.parse import urlencode, parse_qs @@ -64,6 +67,11 @@ class Payment(PaymentCommon): ], } + REFDET_RE = re.compile('^[a-zA-Z0-9]{6,30}$') + + def _generate_refdet(self): + return '%s%10d' % (isonow(), random.randint(1, 1000000000)) + def request(self, amount, next_url=None, exer=None, orderid=None, refdet=None, objet=None, email=None, saisie=None, **kwargs): try: @@ -95,22 +103,30 @@ class Payment(PaymentCommon): raise ValueError() except ValueError: raise ValueError('EXER format invalide') - try: - refdet = orderid or refdet - refdet = str(refdet) - if 6 > len(refdet) > 30: - raise ValueError('len(REFDET) < 6 or > 30') - except Exception as e: - raise ValueError('REFDET format invalide, %r' % refdet, e) + assert not (orderid and refdet), 'orderid and refdet cannot be used together' + # check or generate refdet + if refdet: + try: + if not self.REFDET_RE.match(refdet): + raise ValueError + except (TypeError, ValueError): + raise ValueError('refdet must be 6 to 30 alphanumeric characters string') + if orderid: + if self.REFDET_RE.match(orderid): + refdet = orderid + else: + objet = orderid + (' ' + objet) if objet else '' + if not refdet: + refdet = self._generate_refdet() + transaction_id = refdet + else: + transaction_id = '%s_%s' % (refdet, random.randint(1, 1000000000)) + # check objet or fix objet if objet is not None: try: - objet = str(objet) + objet = objet.encode('ascii') except Exception as e: - raise ValueError('OBJET must be a string', e) - if not objet.replace(' ', '').isalnum(): - raise ValueError('OBJECT must only contains alphanumeric characters, %r' % objet) - if len(objet) > 99: - raise ValueError('OBJET length must be less than 100') + raise ValueError('OBJET must be an alphanumeric string', e) try: mel = str(email) if '@' not in mel: @@ -120,27 +136,22 @@ class Payment(PaymentCommon): except Exception as e: raise ValueError('MEL is not a valid email, %r' % mel, e) + # check saisie saisie = saisie or self.saisie - if saisie not in ('M', 'T', 'X', 'A'): raise ValueError('SAISIE invalid format, %r, must be M, T, X or A' % saisie) - iso_now = isonow() - transaction_id = '%s_%s' % (iso_now, refdet) - if objet: - objet = objet[:100 - len(iso_now) - 2] + ' ' + iso_now - else: - objet = iso_now params = { 'numcli': self.numcli, 'refdet': refdet, 'montant': montant, 'mel': mel, 'saisie': saisie, - 'objet': objet, } if exer: params['exer'] = exer + if objet: + params['objet'] = objet if automatic_return_url: params['urlcl'] = automatic_return_url url = '%s?%s' % (self.service_url, urlencode(params)) diff --git a/tests/test_tipi.py b/tests/test_tipi.py index 63c3902..cf30540 100644 --- a/tests/test_tipi.py +++ b/tests/test_tipi.py @@ -1,7 +1,9 @@ +import datetime from decimal import Decimal from six.moves.urllib.parse import urlparse, parse_qs import eopayment +import eopayment.tipi import pytest @@ -32,3 +34,60 @@ def test_tipi(): with pytest.raises(eopayment.ResponseError, match='missing refdet or resultrans'): p.response('foo=bar') + + +def test_tipi_no_orderid_no_refdet(): + p = eopayment.Payment('tipi', {'numcli': '12345'}) + payment_id, kind, url = p.request( + amount=Decimal('123.12'), + exer=9999, + email='info@entrouvert.com', + saisie='T') + parsed_qs = parse_qs(urlparse(url).query) + assert 'objet' not in parsed_qs + assert parsed_qs['montant'] == ['12312'] + assert parsed_qs['saisie'] == ['T'] + assert parsed_qs['mel'] == ['info@entrouvert.com'] + assert parsed_qs['numcli'] == ['12345'] + assert parsed_qs['exer'] == ['9999'] + assert parsed_qs['refdet'][0].startswith(datetime.datetime.now().strftime('%Y%m%d')) + + +def test_tipi_orderid_refdef_compatible(): + p = eopayment.Payment('tipi', {'numcli': '12345', 'saisie': 'A'}) + payment_id, kind, url = p.request( + amount=Decimal('123.12'), + email='info@entrouvert.com', + orderid='F121212') + expected_url = urlparse(eopayment.tipi.TIPI_URL) + parsed_url = urlparse(url) + assert parsed_url[:3] == expected_url[:3] + parsed_qs = parse_qs(parsed_url.query) + assert 'objet' not in parsed_qs + assert 'exer' not in parsed_qs + assert parsed_qs['montant'] == ['12312'] + assert parsed_qs['saisie'] == ['A'] + assert parsed_qs['mel'] == ['info@entrouvert.com'] + assert parsed_qs['numcli'] == ['12345'] + assert parsed_qs['refdet'] == ['F121212'] + + +def test_tipi_orderid_not_refdef_compatible(): + p = eopayment.Payment('tipi', {'numcli': '12345', 'saisie': 'A'}) + payment_id, kind, url = p.request( + amount=Decimal('123.12'), + email='info@entrouvert.com', + objet='coucou', + orderid='F12-12-12') + expected_url = urlparse(eopayment.tipi.TIPI_URL) + parsed_url = urlparse(url) + assert parsed_url[:3] == expected_url[:3] + parsed_qs = parse_qs(parsed_url.query) + assert 'exer' not in parsed_qs + assert parsed_qs['montant'] == ['12312'] + assert parsed_qs['saisie'] == ['A'] + assert parsed_qs['mel'] == ['info@entrouvert.com'] + assert parsed_qs['numcli'] == ['12345'] + assert parsed_qs['refdet'][0].startswith(datetime.datetime.now().strftime('%Y%m%d')) + assert 'coucou' in parsed_qs['objet'][0] + assert 'F12-12-12' in parsed_qs['objet'][0]