Projet

Général

Profil

0001-payfip_ws-add-new-request-parameters-48135.patch

Benjamin Dauvergne, 03 novembre 2020 12:18

Télécharger (14,5 ko)

Voir les différences:

Subject: [PATCH] payfip_ws: add new request() parameters (#48135)

The added parameters are :
* subject, to pass the description of a payment,
* orderid, to pass the order number for the payment,
* transaction_id, to identify a payment with an external identifier,
* exer, custom field for PayFiP.
 eopayment/payfip_ws.py  |  50 +++++++++--
 tests/test_payfip_ws.py | 184 +++++++++++++++++++++++++++++++++-------
 2 files changed, 198 insertions(+), 36 deletions(-)
eopayment/payfip_ws.py
18 18

  
19 19
import copy
20 20
import datetime
21
from decimal import Decimal, ROUND_DOWN
22 21
import functools
23 22
import os
24 23
import random
24
import re
25
import unicodedata
25 26
import xml.etree.ElementTree as ET
26 27

  
27 28
from gettext import gettext as _
......
37 38
from .systempayv2 import isonow
38 39
from .common import (PaymentCommon, PaymentResponse, URL, PAID, DENIED,
39 40
                     CANCELLED, ERROR, ResponseError, PaymentException,
40
                     WAITING, EXPIRED)
41
                     WAITING, EXPIRED, force_text)
41 42

  
42 43
WSDL_URL = 'https://www.tipi.budget.gouv.fr/tpa/services/mas_securite/contrat_paiement_securise/PaiementSecuriseService?wsdl'  # noqa: E501
43 44

  
......
45 46

  
46 47
PAYMENT_URL = 'https://www.tipi.budget.gouv.fr/tpa/paiementws.web'
47 48

  
49
REFDET_RE = re.compile(r'^[A-Za-z0-9]{1,30}$')
50

  
48 51

  
49 52
def clear_namespace(element):
50 53
    def helper(element):
......
58 61
    return element
59 62

  
60 63

  
64
def normalize_objet(objet):
65
    '''Make objet a string of 100 chars in alphabet [A-Za-z0-9 ]'''
66
    if not objet:
67
        return objet
68

  
69
    objet = force_text(objet)
70
    objet = unicodedata.normalize('NFKD', objet).encode('ascii', 'ignore').decode()
71
    objet = re.sub(r'[^A-Za-z0-9 ]', '', objet).strip()
72
    objet = re.sub(r'[\s]+', ' ', objet)
73
    return objet[:100]
74

  
75

  
61 76
class PayFiPError(PaymentException):
62 77
    def __init__(self, code, message, origin=None):
63 78
        self.code = code
......
200 215
    def _generate_refdet(self):
201 216
        return '%s%010d' % (isonow(), random.randint(1, 1000000000))
202 217

  
203
    def request(self, amount, email, **kwargs):
218
    def request(self, amount, email, refdet=None, exer=None, orderid=None,
219
                subject=None, transaction_id=None, **kwargs):
204 220
        montant = self.clean_amount(amount, max_amount=100000)
205 221

  
206 222
        numcli = self.numcli
207 223
        urlnotif = self.automatic_return_url
208 224
        urlredirect = self.normal_return_url
209
        exer = str(datetime.date.today().year)
210
        refdet = kwargs.get('refdet', self._generate_refdet())
225

  
226
        if not exer:
227
            exer = str(datetime.date.today().year)
228

  
229
        if refdet:
230
            pass
231
        elif transaction_id and REFDET_RE.match(transaction_id):
232
            refdet = transaction_id
233
        elif orderid and REFDET_RE.match(orderid):
234
            refdet = orderid
235
        else:
236
            refdet = self._generate_refdet()
237

  
238
        objet_parts = []
239
        if orderid and refdet != orderid:
240
            objet_parts.extend(['O', orderid])
241
        if subject:
242
            if objet_parts:
243
                objet_parts.append('S')
244
            objet_parts.append(subject)
245
        if transaction_id and refdet != transaction_id:
246
            objet_parts.extend(['T', transaction_id])
247
        objet = normalize_objet(' '.join(objet_parts))
248

  
211 249
        mel = email
212 250
        if hasattr(mel, 'decode'):
213 251
            mel = email.decode('ascii')
214

  
215 252
        try:
216 253
            if '@' not in mel:
217 254
                raise ValueError('no @ in MEL')
......
227 264

  
228 265
        idop = self.payfip.get_idop(numcli=numcli, saisie=saisie, exer=exer,
229 266
                                    refdet=refdet, montant=montant, mel=mel,
267
                                    objet=objet or None,
230 268
                                    url_notification=urlnotif,
231 269
                                    url_redirect=urlredirect)
232 270

  
tests/test_payfip_ws.py
21 21
import datetime
22 22
import json
23 23
import lxml.etree as ET
24
import mock
24 25

  
25 26
import pytz
26 27

  
......
30 31
from zeep.plugins import HistoryPlugin
31 32

  
32 33
import eopayment
33
from eopayment.payfip_ws import PayFiP, PayFiPError
34
from eopayment.payfip_ws import PayFiP, PayFiPError, normalize_objet
35

  
36

  
37
NUMCLI = '090909'
38
NOTIF_URL = 'https://notif.payfip.example.com/'
39
REDIRECT_URL = 'https://redirect.payfip.example.com/'
40
MEL = 'john.doe@example.com'
41
EXER = '2019'
42
REFDET = '201912261758460053903194'
43
REFDET_GEN = '201912261758460053903195'
34 44

  
35 45

  
36 46
def xmlindent(content):
......
38 48
        content = ET.fromstring(content)
39 49
    return ET.tostring(content, pretty_print=True).decode('utf-8', 'ignore')
40 50

  
41
NUMCLI = '090909'
42

  
43 51

  
44 52
# freeze time to fix EXER field to 2019
45 53
@pytest.fixture(autouse=True)
......
48 56
    return freezer
49 57

  
50 58

  
51
@pytest.fixture
52
def backend(history):
53
    return eopayment.Payment('payfip_ws', {
54
        'numcli': '090909',
55
        'automatic_return_url': NOTIF_URL,
56
        'normal_return_url': REDIRECT_URL,
57
    })
58

  
59

  
60 59
class PayFiPHTTMock(object):
61 60
    def __init__(self, history_name):
62 61
        history_path = 'tests/data/payfip-%s.json' % history_name
......
87 86
        yield None
88 87

  
89 88

  
89
@pytest.fixture
90
def get_idop():
91
    with mock.patch('eopayment.payfip_ws.PayFiP.get_idop') as get_idop:
92
        get_idop.return_value = 'idop-1234'
93
        yield get_idop
94

  
95

  
96
@pytest.fixture
97
def backend(request):
98
    with mock.patch('eopayment.payfip_ws.Payment._generate_refdet') as _generate_refdet:
99
        _generate_refdet.return_value = REFDET_GEN
100
        yield eopayment.Payment('payfip_ws', {
101
            'numcli': '090909',
102
            'automatic_return_url': NOTIF_URL,
103
            'normal_return_url': REDIRECT_URL,
104
        })
105

  
106

  
90 107
@pytest.fixture
91 108
def payfip(history, history_name, request):
92 109
    history = HistoryPlugin()
......
130 147
    assert result.numcli == NUMCLI
131 148
    assert result.libelleN2 == 'POUETPOUET'
132 149

  
133
NOTIF_URL = 'https://notif.payfip.example.com/'
134
REDIRECT_URL = 'https://redirect.payfip.example.com/'
135

  
136 150

  
137 151
def test_get_idop_ok(payfip):
138 152
    result = payfip.get_idop(
......
140 154
        exer='2019',
141 155
        refdet='ABCDEFGH',
142 156
        montant='1000',
143
        mel='john.doe@example.com',
157
        mel=MEL,
144 158
        objet='coucou',
145 159
        url_notification=NOTIF_URL,
146 160
        url_redirect=REDIRECT_URL,
......
155 169
            exer='2019',
156 170
            refdet='ABCD',
157 171
            montant='1000',
158
            mel='john.doe@example.com',
172
            mel=MEL,
159 173
            objet='coucou',
160 174
            url_notification='https://notif.payfip.example.com/',
161 175
            url_redirect='https://redirect.payfip.example.com/',
......
183 197
        'exer': '20',
184 198
        'heurtrans': '1311',
185 199
        'idOp': 'cc0cb210-1cd4-11ea-8cca-0213ad91a103',
186
        'mel': 'john.doe@example.com',
200
        'mel': MEL,
187 201
        'montant': '1000',
188 202
        'numauto': '112233445566-tip',
189 203
        'numcli': NUMCLI,
......
201 215

  
202 216

  
203 217
@set_history_name('test_get_info_paiement_P1')
204
def test_P1_and_payment_status(payfip, backend, freezer):
218
def test_P1_and_payment_status(history, backend):
205 219
    response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103')
206 220
    assert response.result == eopayment.EXPIRED
207 221

  
208 222

  
209 223
@set_history_name('test_get_info_paiement_P1')
210
def test_P1_and_payment_status_utc_aware_now(payfip, backend, freezer):
224
def test_P1_and_payment_status_utc_aware_now(history, backend):
211 225
    utc_now = datetime.datetime.now(pytz.utc)
212 226
    response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=utc_now)
213 227
    assert response.result == eopayment.EXPIRED
214 228

  
215 229

  
216 230
@set_history_name('test_get_info_paiement_P1')
217
def test_P1_and_payment_status_utc_naive_now(payfip, backend, freezer):
231
def test_P1_and_payment_status_utc_naive_now(history, backend):
218 232
    now = datetime.datetime.now()
219 233
    response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=now)
220 234
    assert response.result == eopayment.EXPIRED
221 235

  
222 236

  
223 237
@set_history_name('test_get_info_paiement_P1')
224
def test_P1_and_payment_status_utc_aware_now_later(payfip, backend, freezer):
238
def test_P1_and_payment_status_utc_aware_now_later(history, backend, freezer):
225 239
    utc_now = datetime.datetime.now(pytz.utc)
226 240
    freezer.move_to(datetime.timedelta(minutes=25))
227 241
    response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=utc_now)
......
281 295
def test_payment_ok(payfip, backend):
282 296
    payment_id, kind, url = backend.request(
283 297
        amount='10.00',
284
        email='john.doe@example.com',
298
        email=MEL,
285 299
        # make test deterministic
286 300
        refdet='201912261758460053903194')
287 301

  
......
298 312

  
299 313

  
300 314
@set_history_name('test_payment_ok')
301
def test_payment_status_ok(backend, freezer, history):
315
def test_payment_status_ok(history, backend, freezer):
302 316
    history.counter = 1  # only the response
303 317
    now = datetime.datetime.now()
304 318
    response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=now)
305 319
    assert response.result == eopayment.PAID
306 320

  
307 321

  
308
def test_payment_denied(backend):
322
def test_payment_denied(history, backend):
309 323
    payment_id, kind, url = backend.request(
310 324
        amount='10.00',
311
        email='john.doe@example.com',
325
        email=MEL,
312 326
        # make test deterministic
313 327
        refdet='201912261758460053903194')
314 328

  
......
324 338

  
325 339

  
326 340
@set_history_name('test_payment_denied')
327
def test_payment_status_denied(backend, freezer, history):
341
def test_payment_status_denied(history, backend, freezer):
328 342
    history.counter = 1  # only the response
329 343
    now = datetime.datetime.now()
330 344
    response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=now)
331 345
    assert response.result == eopayment.DENIED
332 346

  
333 347

  
334
def test_payment_cancelled(backend):
348
def test_payment_cancelled(history, backend):
335 349
    payment_id, kind, url = backend.request(
336 350
        amount='10.00',
337
        email='john.doe@example.com',
351
        email=MEL,
338 352
        # make test deterministic
339 353
        refdet='201912261758460053903194')
340 354

  
......
350 364

  
351 365

  
352 366
@set_history_name('test_payment_cancelled')
353
def test_payment_status_cancelled(backend, freezer, history):
367
def test_payment_status_cancelled(history, backend, freezer):
354 368
    history.counter = 1  # only the response
355 369
    now = datetime.datetime.now()
356 370
    response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=now)
357 371
    assert response.result == eopayment.CANCELLED
372

  
373

  
374
def test_normalize_objet():
375
    assert normalize_objet(None) is None
376
    assert (
377
        normalize_objet('18/09/2020  Établissement attestation hors-sol n#1234')
378
        == '18092020 Etablissement attestation horssol n1234'
379
    )
380

  
381

  
382
def test_refdet_exer(get_idop, backend):
383
    payment_id, kind, url = backend.request(
384
        amount='10.00',
385
        email=MEL,
386
        # make test deterministic
387
        exer=EXER,
388
        refdet=REFDET)
389

  
390
    assert payment_id == 'idop-1234'
391
    kwargs = get_idop.call_args[1]
392

  
393
    assert kwargs == {
394
        'exer': EXER,
395
        'refdet': REFDET,
396
        'montant': '1000',
397
        'objet': None,
398
        'mel': MEL,
399
        'numcli': NUMCLI,
400
        'saisie': 'T',
401
        'url_notification': NOTIF_URL,
402
        'url_redirect': REDIRECT_URL,
403
    }
404

  
405

  
406
def test_transaction_id_orderid_subject(get_idop, backend):
407
    payment_id, kind, url = backend.request(
408
        amount='10.00',
409
        email=MEL,
410
        # make test deterministic
411
        exer=EXER,
412
        transaction_id='TR12345',
413
        orderid='F20190003',
414
        subject='Précompte famille #1234')
415

  
416
    assert payment_id == 'idop-1234'
417
    kwargs = get_idop.call_args[1]
418

  
419
    assert kwargs == {
420
        'exer': EXER,
421
        'refdet': 'TR12345',
422
        'montant': '1000',
423
        'objet': 'O F20190003 S Precompte famille 1234',
424
        'mel': MEL,
425
        'numcli': NUMCLI,
426
        'saisie': 'T',
427
        'url_notification': NOTIF_URL,
428
        'url_redirect': REDIRECT_URL,
429
    }
430

  
431

  
432
def test_invalid_transaction_id_valid_orderid(get_idop, backend):
433
    payment_id, kind, url = backend.request(
434
        amount='10.00',
435
        email=MEL,
436
        # make test deterministic
437
        exer=EXER,
438
        transaction_id='TR-12345',
439
        orderid='F20190003',
440
        subject='Précompte famille #1234')
441

  
442
    assert payment_id == 'idop-1234'
443
    kwargs = get_idop.call_args[1]
444

  
445
    assert kwargs == {
446
        'exer': EXER,
447
        'refdet': 'F20190003',
448
        'montant': '1000',
449
        'objet': 'Precompte famille 1234 T TR12345',
450
        'mel': MEL,
451
        'numcli': NUMCLI,
452
        'saisie': 'T',
453
        'url_notification': NOTIF_URL,
454
        'url_redirect': REDIRECT_URL,
455
    }
456

  
457

  
458
def test_invalid_transaction_id_invalid_orderid(get_idop, backend):
459
    payment_id, kind, url = backend.request(
460
        amount='10.00',
461
        email=MEL,
462
        # make test deterministic
463
        exer=EXER,
464
        transaction_id='TR-12345',
465
        orderid='F/20190003',
466
        subject='Précompte famille #1234')
467

  
468
    assert payment_id == 'idop-1234'
469
    kwargs = get_idop.call_args[1]
470

  
471
    assert kwargs == {
472
        'exer': EXER,
473
        'refdet': REFDET_GEN,
474
        'montant': '1000',
475
        'objet': 'O F20190003 S Precompte famille 1234 T TR12345',
476
        'mel': MEL,
477
        'numcli': NUMCLI,
478
        'saisie': 'T',
479
        'url_notification': NOTIF_URL,
480
        'url_redirect': REDIRECT_URL,
481
    }
358
-