Projet

Général

Profil

0003-payfip_ws-implement-payment_status-47670.patch

Benjamin Dauvergne, 03 novembre 2020 11:22

Télécharger (17,6 ko)

Voir les différences:

Subject: [PATCH 3/3] payfip_ws: implement payment_status (#47670)

The response() method is also refactored around payment_status().
 eopayment/payfip_ws.py  |  52 +++++++-
 tests/test_payfip_ws.py | 278 +++++++++++++++++++++++++++-------------
 2 files changed, 239 insertions(+), 91 deletions(-)
eopayment/payfip_ws.py
26 26

  
27 27
from gettext import gettext as _
28 28

  
29
import pytz
30

  
29 31
import six
30 32
from six.moves.urllib.parse import parse_qs
31 33

  
......
34 36

  
35 37
from .systempayv2 import isonow
36 38
from .common import (PaymentCommon, PaymentResponse, URL, PAID, DENIED,
37
                     CANCELLED, ERROR, ResponseError, PaymentException)
39
                     CANCELLED, ERROR, ResponseError, PaymentException,
40
                     WAITING, EXPIRED)
38 41

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

  
......
229 232

  
230 233
        return str(idop), URL, self.payment_url + '?idop=%s' % idop
231 234

  
235
    def payment_status(self, transaction_id, transaction_date=None, **kwargs):
236
        # idop are valid for 15 minutes after their generation
237
        # between generation and payment, any call to get_info_paiement() will return a PayFiPError with code=P5
238
        # before the end of the 15 minutes it can mean the payment is in progress
239
        # after the 15 minutes period it means the payment will never happen,
240
        # and after one day the code will change for P1, meaning the idop is
241
        # now unknown as it as been cleaned by the night cleaning job.
242
        #
243
        # So in order to interpret the meaning of PayFiP error codes we need
244
        # the date of the start of the transaction and add to it some margin
245
        # to.
246
        idop = transaction_id
247
        if transaction_date:
248
            if transaction_date.tzinfo:  # date is aware
249
                now = datetime.datetime.now(tz=pytz.utc)
250
            else:
251
                now = datetime.datetime.now()
252
            delta = now - transaction_date
253
        else:
254
            delta = datetime.timedelta(seconds=0)
255
        # set the threshold between transaction 'in progress' and 'expired' at 20 minutes
256
        threshold = datetime.timedelta(seconds=20 * 60)
257

  
258
        try:
259
            response = self.payfip.get_info_paiement(idop)
260
        except PayFiPError as e:
261
            if e.code == 'P1' or (
262
                    e.code == 'P5' and delta >= threshold):
263
                return PaymentResponse(
264
                    result=EXPIRED,
265
                    signed=True,
266
                    order_id=transaction_id)
267
            if e.code == 'P5' and delta < threshold:
268
                return PaymentResponse(
269
                    result=WAITING,
270
                    signed=True,
271
                    order_id=transaction_id)
272
            raise e
273
        return self.payfip_response_to_eopayment_response(idop, response)
274

  
232 275
    def response(self, query_string, **kwargs):
233 276
        fields = parse_qs(query_string, True)
234 277
        idop = (fields.get('idop') or [None])[0]
......
236 279
        if not idop:
237 280
            raise ResponseError('missing idop parameter in query string')
238 281

  
239
        try:
240
            response = self.payfip.get_info_paiement(idop)
241
        except PayFiPError as e:
242
            raise ResponseError('invalid return from payfip', e)
282
        return self.payment_status(idop)
243 283

  
284
    @classmethod
285
    def payfip_response_to_eopayment_response(cls, idop, response):
244 286
        if response.resultrans == 'P':
245 287
            result = PAID
246 288
            bank_status = 'paid CB'
tests/test_payfip_ws.py
18 18

  
19 19
from __future__ import print_function, unicode_literals
20 20

  
21
import datetime
21 22
import json
22 23
import lxml.etree as ET
23 24

  
25
import pytz
26

  
24 27
import httmock
25 28
import pytest
26 29

  
......
42 45
@pytest.fixture(autouse=True)
43 46
def freezer(freezer):
44 47
    freezer.move_to('2019-12-12')
48
    return freezer
49

  
50

  
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
    })
45 58

  
46 59

  
47 60
class PayFiPHTTMock(object):
48
    def __init__(self, request):
49
        history_path = 'tests/data/payfip-%s.json' % request.function.__name__
61
    def __init__(self, history_name):
62
        history_path = 'tests/data/payfip-%s.json' % history_name
50 63
        with open(history_path) as fd:
51 64
            self.history = json.load(fd)
52 65
        self.counter = 0
53 66

  
54
    @httmock.urlmatch()
67
    @httmock.urlmatch(scheme='https')
55 68
    def mock(self, url, request):
56 69
        request_content, response_content = self.history[self.counter]
57 70
        self.counter += 1
......
60 73

  
61 74

  
62 75
@pytest.fixture
63
def payfip(request):
76
def history_name(request):
77
    return getattr(request.function, 'history_name', request.function.__name__)
78

  
79

  
80
@pytest.fixture
81
def history(history_name, request):
82
    if 'update_data' not in request.keywords:
83
        history_mock = PayFiPHTTMock(history_name)
84
        with httmock.HTTMock(history_mock.mock):
85
            yield history_mock
86
    else:
87
        yield None
88

  
89

  
90
@pytest.fixture
91
def payfip(history, history_name, request):
64 92
    history = HistoryPlugin()
65 93

  
66 94
    @httmock.urlmatch()
......
72 100
    with httmock.HTTMock(raise_on_request):
73 101
        payfip = PayFiP(wsdl_url='./eopayment/resource/PaiementSecuriseService.wsdl',
74 102
                        zeep_client_kwargs={'plugins': [history]})
75
    try:
76
        if 'update_data' not in request.keywords:
77
            with httmock.HTTMock(PayFiPHTTMock(request).mock):
78
                yield payfip
79
        else:
80
            yield payfip
81
    finally:
82
        # add @pytest.mark.update_data to test to update fixtures data
83
        if 'update_data' in request.keywords:
84
            history_path = 'tests/data/payfip-%s.json' % request.function.__name__
85
            d = [
86
                (xmlindent(exchange['sent']['envelope']),
87
                 xmlindent(exchange['received']['envelope']))
88
                for exchange in history._buffer
89
            ]
90
            content = json.dumps(d)
91
            with open(history_path, 'wb') as fd:
92
                fd.write(content)
103
    yield payfip
104

  
105
    # add @pytest.mark.update_data to test to update fixtures data
106
    if 'update_data' in request.keywords:
107
        history_path = 'tests/data/payfip-%s.json' % history_name
108
        d = [
109
            (xmlindent(exchange['sent']['envelope']),
110
             xmlindent(exchange['received']['envelope']))
111
            for exchange in history._buffer
112
        ]
113
        content = json.dumps(d)
114
        with open(history_path, 'wb') as fd:
115
            fd.write(content)
116

  
117

  
118
def set_history_name(name):
119
    # decorator to add history_name to test
120
    def decorator(func):
121
        func.history_name = name
122
        return func
123
    return decorator
93 124

  
94 125
# pytestmark = pytest.mark.update_data
95 126

  
......
163 194
    }
164 195

  
165 196

  
166
def test_get_info_paiement_P1(payfip):
197
def test_get_info_paiement_P1(payfip, freezer):
167 198
    # idop par pas encore reçu par la plate-forme ou déjà nettoyé (la nuit)
168 199
    with pytest.raises(PayFiPError, match='.*P1.*IdOp incorrect.*'):
169 200
        payfip.get_info_paiement('cc0cb210-1cd4-11ea-8cca-0213ad91a103')
170 201

  
171 202

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

  
208

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

  
215

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

  
222

  
223
@set_history_name('test_get_info_paiement_P1')
224
def test_P1_and_payment_status_utc_aware_now_later(payfip, backend, freezer):
225
    utc_now = datetime.datetime.now(pytz.utc)
226
    freezer.move_to(datetime.timedelta(minutes=25))
227
    response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=utc_now)
228
    assert response.result == eopayment.EXPIRED
229

  
230

  
231
@set_history_name('test_get_info_paiement_P1')
232
def test_P1_and_payment_status_utc_naive_now_later(payfip, backend, freezer):
233
    now = datetime.datetime.now()
234
    freezer.move_to(datetime.timedelta(minutes=25))
235
    response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=now)
236
    assert response.result == eopayment.EXPIRED
237

  
238

  
172 239
def test_get_info_paiement_P5(payfip):
173 240
    # idop reçu par la plate-forme mais transaction en cours
174 241
    with pytest.raises(PayFiPError, match='.*P5.*sultat de la transaction non connu.*'):
175 242
        payfip.get_info_paiement('cc0cb210-1cd4-11ea-8cca-0213ad91a103')
176 243

  
177 244

  
178
def test_payment_ok(request):
179
    payment = eopayment.Payment('payfip_ws', {
180
        'numcli': '090909',
181
        'automatic_return_url': NOTIF_URL,
182
        'normal_return_url': REDIRECT_URL,
183
    })
245
@set_history_name('test_get_info_paiement_P5')
246
def test_P5_and_payment_status(payfip, backend, freezer):
247
    response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103')
248
    assert response.result == eopayment.WAITING
184 249

  
185
    with httmock.HTTMock(PayFiPHTTMock(request).mock):
186
        payment_id, kind, url = payment.request(
187
            amount='10.00',
188
            email='john.doe@example.com',
189
            # make test deterministic
190
            refdet='201912261758460053903194')
191 250

  
192
        assert payment_id == 'cc0cb210-1cd4-11ea-8cca-0213ad91a103'
193
        assert kind == eopayment.URL
194
        assert url == 'https://www.tipi.budget.gouv.fr/tpa/paiementws.web?idop=cc0cb210-1cd4-11ea-8cca-0213ad91a103'
251
@set_history_name('test_get_info_paiement_P5')
252
def test_P5_and_payment_status_utc_aware_now(payfip, backend, freezer):
253
    utc_now = datetime.datetime.now(pytz.utc)
254
    response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=utc_now)
255
    assert response.result == eopayment.WAITING
195 256

  
196
        response = payment.response('idop=%s' % payment_id)
197
        assert response.result == eopayment.PAID
198
        assert response.bank_status == 'paid CB'
199
        assert response.order_id == payment_id
200
        assert response.transaction_id == (
201
            '201912261758460053903194 cc0cb210-1cd4-11ea-8cca-0213ad91a103 112233445566-tip')
202 257

  
258
@set_history_name('test_get_info_paiement_P5')
259
def test_P5_and_payment_status_utc_naive_now(payfip, backend, freezer):
260
    now = datetime.datetime.now()
261
    response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=now)
262
    assert response.result == eopayment.WAITING
203 263

  
204
def test_payment_denied(request):
205
    payment = eopayment.Payment('payfip_ws', {
206
        'numcli': '090909',
207
        'automatic_return_url': NOTIF_URL,
208
        'normal_return_url': REDIRECT_URL,
209
    })
210 264

  
211
    with httmock.HTTMock(PayFiPHTTMock(request).mock):
212
        payment_id, kind, url = payment.request(
213
            amount='10.00',
214
            email='john.doe@example.com',
215
            # make test deterministic
216
            refdet='201912261758460053903194')
265
@set_history_name('test_get_info_paiement_P5')
266
def test_P5_and_payment_status_utc_aware_now_later(payfip, backend, freezer):
267
    utc_now = datetime.datetime.now(pytz.utc)
268
    freezer.move_to(datetime.timedelta(minutes=25))
269
    response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=utc_now)
270
    assert response.result == eopayment.EXPIRED
217 271

  
218
        assert payment_id == 'cc0cb210-1cd4-11ea-8cca-0213ad91a103'
219
        assert kind == eopayment.URL
220
        assert url == 'https://www.tipi.budget.gouv.fr/tpa/paiementws.web?idop=cc0cb210-1cd4-11ea-8cca-0213ad91a103'
221 272

  
222
        response = payment.response('idop=%s' % payment_id)
223
        assert response.result == eopayment.DENIED
224
        assert response.bank_status == 'refused CB'
225
        assert response.order_id == payment_id
226
        assert response.transaction_id == '201912261758460053903194 cc0cb210-1cd4-11ea-8cca-0213ad91a103'
273
@set_history_name('test_get_info_paiement_P5')
274
def test_P5_and_payment_status_utc_naive_now_later(payfip, backend, freezer):
275
    now = datetime.datetime.now()
276
    freezer.move_to(datetime.timedelta(minutes=25))
277
    response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=now)
278
    assert response.result == eopayment.EXPIRED
227 279

  
228 280

  
229
def test_payment_cancelled(request):
230
    payment = eopayment.Payment('payfip_ws', {
231
        'numcli': '090909',
232
        'automatic_return_url': NOTIF_URL,
233
        'normal_return_url': REDIRECT_URL,
234
    })
281
def test_payment_ok(payfip, backend):
282
    payment_id, kind, url = backend.request(
283
        amount='10.00',
284
        email='john.doe@example.com',
285
        # make test deterministic
286
        refdet='201912261758460053903194')
287

  
288
    assert payment_id == 'cc0cb210-1cd4-11ea-8cca-0213ad91a103'
289
    assert kind == eopayment.URL
290
    assert url == 'https://www.tipi.budget.gouv.fr/tpa/paiementws.web?idop=cc0cb210-1cd4-11ea-8cca-0213ad91a103'
291

  
292
    response = backend.response('idop=%s' % payment_id)
293
    assert response.result == eopayment.PAID
294
    assert response.bank_status == 'paid CB'
295
    assert response.order_id == payment_id
296
    assert response.transaction_id == (
297
        '201912261758460053903194 cc0cb210-1cd4-11ea-8cca-0213ad91a103 112233445566-tip')
298

  
299

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

  
307

  
308
def test_payment_denied(backend):
309
    payment_id, kind, url = backend.request(
310
        amount='10.00',
311
        email='john.doe@example.com',
312
        # make test deterministic
313
        refdet='201912261758460053903194')
314

  
315
    assert payment_id == 'cc0cb210-1cd4-11ea-8cca-0213ad91a103'
316
    assert kind == eopayment.URL
317
    assert url == 'https://www.tipi.budget.gouv.fr/tpa/paiementws.web?idop=cc0cb210-1cd4-11ea-8cca-0213ad91a103'
318

  
319
    response = backend.response('idop=%s' % payment_id)
320
    assert response.result == eopayment.DENIED
321
    assert response.bank_status == 'refused CB'
322
    assert response.order_id == payment_id
323
    assert response.transaction_id == '201912261758460053903194 cc0cb210-1cd4-11ea-8cca-0213ad91a103'
324

  
325

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

  
333

  
334
def test_payment_cancelled(backend):
335
    payment_id, kind, url = backend.request(
336
        amount='10.00',
337
        email='john.doe@example.com',
338
        # make test deterministic
339
        refdet='201912261758460053903194')
235 340

  
236
    with httmock.HTTMock(PayFiPHTTMock(request).mock):
237
        payment_id, kind, url = payment.request(
238
            amount='10.00',
239
            email='john.doe@example.com',
240
            # make test deterministic
241
            refdet='201912261758460053903194')
242

  
243
        assert payment_id == 'cc0cb210-1cd4-11ea-8cca-0213ad91a103'
244
        assert kind == eopayment.URL
245
        assert url == 'https://www.tipi.budget.gouv.fr/tpa/paiementws.web?idop=cc0cb210-1cd4-11ea-8cca-0213ad91a103'
246

  
247
        response = payment.response('idop=%s' % payment_id)
248
        assert response.result == eopayment.CANCELLED
249
        assert response.bank_status == 'cancelled CB'
250
        assert response.order_id == payment_id
251
        assert response.transaction_id == '201912261758460053903194 cc0cb210-1cd4-11ea-8cca-0213ad91a103'
341
    assert payment_id == 'cc0cb210-1cd4-11ea-8cca-0213ad91a103'
342
    assert kind == eopayment.URL
343
    assert url == 'https://www.tipi.budget.gouv.fr/tpa/paiementws.web?idop=cc0cb210-1cd4-11ea-8cca-0213ad91a103'
344

  
345
    response = backend.response('idop=%s' % payment_id)
346
    assert response.result == eopayment.CANCELLED
347
    assert response.bank_status == 'cancelled CB'
348
    assert response.order_id == payment_id
349
    assert response.transaction_id == '201912261758460053903194 cc0cb210-1cd4-11ea-8cca-0213ad91a103'
350

  
351

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