payfip_ws: implement payment_status (#47670)
The response() method is also refactored around payment_status().
This commit is contained in:
parent
5b5f4c165b
commit
5bf49c33ba
|
@ -26,6 +26,8 @@ import xml.etree.ElementTree as ET
|
|||
|
||||
from gettext import gettext as _
|
||||
|
||||
import pytz
|
||||
|
||||
import six
|
||||
from six.moves.urllib.parse import parse_qs
|
||||
|
||||
|
@ -34,7 +36,8 @@ import zeep.exceptions
|
|||
|
||||
from .systempayv2 import isonow
|
||||
from .common import (PaymentCommon, PaymentResponse, URL, PAID, DENIED,
|
||||
CANCELLED, ERROR, ResponseError, PaymentException)
|
||||
CANCELLED, ERROR, ResponseError, PaymentException,
|
||||
WAITING, EXPIRED)
|
||||
|
||||
WSDL_URL = 'https://www.tipi.budget.gouv.fr/tpa/services/mas_securite/contrat_paiement_securise/PaiementSecuriseService?wsdl' # noqa: E501
|
||||
|
||||
|
@ -229,6 +232,46 @@ class Payment(PaymentCommon):
|
|||
|
||||
return str(idop), URL, self.payment_url + '?idop=%s' % idop
|
||||
|
||||
def payment_status(self, transaction_id, transaction_date=None, **kwargs):
|
||||
# idop are valid for 15 minutes after their generation
|
||||
# between generation and payment, any call to get_info_paiement() will return a PayFiPError with code=P5
|
||||
# before the end of the 15 minutes it can mean the payment is in progress
|
||||
# after the 15 minutes period it means the payment will never happen,
|
||||
# and after one day the code will change for P1, meaning the idop is
|
||||
# now unknown as it as been cleaned by the night cleaning job.
|
||||
#
|
||||
# So in order to interpret the meaning of PayFiP error codes we need
|
||||
# the date of the start of the transaction and add to it some margin
|
||||
# to.
|
||||
idop = transaction_id
|
||||
if transaction_date:
|
||||
if transaction_date.tzinfo: # date is aware
|
||||
now = datetime.datetime.now(tz=pytz.utc)
|
||||
else:
|
||||
now = datetime.datetime.now()
|
||||
delta = now - transaction_date
|
||||
else:
|
||||
delta = datetime.timedelta(seconds=0)
|
||||
# set the threshold between transaction 'in progress' and 'expired' at 20 minutes
|
||||
threshold = datetime.timedelta(seconds=20 * 60)
|
||||
|
||||
try:
|
||||
response = self.payfip.get_info_paiement(idop)
|
||||
except PayFiPError as e:
|
||||
if e.code == 'P1' or (
|
||||
e.code == 'P5' and delta >= threshold):
|
||||
return PaymentResponse(
|
||||
result=EXPIRED,
|
||||
signed=True,
|
||||
order_id=transaction_id)
|
||||
if e.code == 'P5' and delta < threshold:
|
||||
return PaymentResponse(
|
||||
result=WAITING,
|
||||
signed=True,
|
||||
order_id=transaction_id)
|
||||
raise e
|
||||
return self.payfip_response_to_eopayment_response(idop, response)
|
||||
|
||||
def response(self, query_string, **kwargs):
|
||||
fields = parse_qs(query_string, True)
|
||||
idop = (fields.get('idop') or [None])[0]
|
||||
|
@ -236,11 +279,10 @@ class Payment(PaymentCommon):
|
|||
if not idop:
|
||||
raise ResponseError('missing idop parameter in query string')
|
||||
|
||||
try:
|
||||
response = self.payfip.get_info_paiement(idop)
|
||||
except PayFiPError as e:
|
||||
raise ResponseError('invalid return from payfip', e)
|
||||
return self.payment_status(idop)
|
||||
|
||||
@classmethod
|
||||
def payfip_response_to_eopayment_response(cls, idop, response):
|
||||
if response.resultrans == 'P':
|
||||
result = PAID
|
||||
bank_status = 'paid CB'
|
||||
|
|
|
@ -18,9 +18,12 @@
|
|||
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import lxml.etree as ET
|
||||
|
||||
import pytz
|
||||
|
||||
import httmock
|
||||
import pytest
|
||||
|
||||
|
@ -42,16 +45,26 @@ NUMCLI = '090909'
|
|||
@pytest.fixture(autouse=True)
|
||||
def freezer(freezer):
|
||||
freezer.move_to('2019-12-12')
|
||||
return freezer
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def backend(history):
|
||||
return eopayment.Payment('payfip_ws', {
|
||||
'numcli': '090909',
|
||||
'automatic_return_url': NOTIF_URL,
|
||||
'normal_return_url': REDIRECT_URL,
|
||||
})
|
||||
|
||||
|
||||
class PayFiPHTTMock(object):
|
||||
def __init__(self, request):
|
||||
history_path = 'tests/data/payfip-%s.json' % request.function.__name__
|
||||
def __init__(self, history_name):
|
||||
history_path = 'tests/data/payfip-%s.json' % history_name
|
||||
with open(history_path) as fd:
|
||||
self.history = json.load(fd)
|
||||
self.counter = 0
|
||||
|
||||
@httmock.urlmatch()
|
||||
@httmock.urlmatch(scheme='https')
|
||||
def mock(self, url, request):
|
||||
request_content, response_content = self.history[self.counter]
|
||||
self.counter += 1
|
||||
|
@ -60,7 +73,22 @@ class PayFiPHTTMock(object):
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def payfip(request):
|
||||
def history_name(request):
|
||||
return getattr(request.function, 'history_name', request.function.__name__)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def history(history_name, request):
|
||||
if 'update_data' not in request.keywords:
|
||||
history_mock = PayFiPHTTMock(history_name)
|
||||
with httmock.HTTMock(history_mock.mock):
|
||||
yield history_mock
|
||||
else:
|
||||
yield None
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def payfip(history, history_name, request):
|
||||
history = HistoryPlugin()
|
||||
|
||||
@httmock.urlmatch()
|
||||
|
@ -72,24 +100,27 @@ def payfip(request):
|
|||
with httmock.HTTMock(raise_on_request):
|
||||
payfip = PayFiP(wsdl_url='./eopayment/resource/PaiementSecuriseService.wsdl',
|
||||
zeep_client_kwargs={'plugins': [history]})
|
||||
try:
|
||||
if 'update_data' not in request.keywords:
|
||||
with httmock.HTTMock(PayFiPHTTMock(request).mock):
|
||||
yield payfip
|
||||
else:
|
||||
yield payfip
|
||||
finally:
|
||||
# add @pytest.mark.update_data to test to update fixtures data
|
||||
if 'update_data' in request.keywords:
|
||||
history_path = 'tests/data/payfip-%s.json' % request.function.__name__
|
||||
d = [
|
||||
(xmlindent(exchange['sent']['envelope']),
|
||||
xmlindent(exchange['received']['envelope']))
|
||||
for exchange in history._buffer
|
||||
]
|
||||
content = json.dumps(d)
|
||||
with open(history_path, 'wb') as fd:
|
||||
fd.write(content)
|
||||
yield payfip
|
||||
|
||||
# add @pytest.mark.update_data to test to update fixtures data
|
||||
if 'update_data' in request.keywords:
|
||||
history_path = 'tests/data/payfip-%s.json' % history_name
|
||||
d = [
|
||||
(xmlindent(exchange['sent']['envelope']),
|
||||
xmlindent(exchange['received']['envelope']))
|
||||
for exchange in history._buffer
|
||||
]
|
||||
content = json.dumps(d)
|
||||
with open(history_path, 'wb') as fd:
|
||||
fd.write(content)
|
||||
|
||||
|
||||
def set_history_name(name):
|
||||
# decorator to add history_name to test
|
||||
def decorator(func):
|
||||
func.history_name = name
|
||||
return func
|
||||
return decorator
|
||||
|
||||
# pytestmark = pytest.mark.update_data
|
||||
|
||||
|
@ -163,89 +194,164 @@ def test_get_info_paiement_ok(payfip):
|
|||
}
|
||||
|
||||
|
||||
def test_get_info_paiement_P1(payfip):
|
||||
def test_get_info_paiement_P1(payfip, freezer):
|
||||
# idop par pas encore reçu par la plate-forme ou déjà nettoyé (la nuit)
|
||||
with pytest.raises(PayFiPError, match='.*P1.*IdOp incorrect.*'):
|
||||
payfip.get_info_paiement('cc0cb210-1cd4-11ea-8cca-0213ad91a103')
|
||||
|
||||
|
||||
@set_history_name('test_get_info_paiement_P1')
|
||||
def test_P1_and_payment_status(payfip, backend, freezer):
|
||||
response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103')
|
||||
assert response.result == eopayment.EXPIRED
|
||||
|
||||
|
||||
@set_history_name('test_get_info_paiement_P1')
|
||||
def test_P1_and_payment_status_utc_aware_now(payfip, backend, freezer):
|
||||
utc_now = datetime.datetime.now(pytz.utc)
|
||||
response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=utc_now)
|
||||
assert response.result == eopayment.EXPIRED
|
||||
|
||||
|
||||
@set_history_name('test_get_info_paiement_P1')
|
||||
def test_P1_and_payment_status_utc_naive_now(payfip, backend, freezer):
|
||||
now = datetime.datetime.now()
|
||||
response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=now)
|
||||
assert response.result == eopayment.EXPIRED
|
||||
|
||||
|
||||
@set_history_name('test_get_info_paiement_P1')
|
||||
def test_P1_and_payment_status_utc_aware_now_later(payfip, backend, freezer):
|
||||
utc_now = datetime.datetime.now(pytz.utc)
|
||||
freezer.move_to(datetime.timedelta(minutes=25))
|
||||
response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=utc_now)
|
||||
assert response.result == eopayment.EXPIRED
|
||||
|
||||
|
||||
@set_history_name('test_get_info_paiement_P1')
|
||||
def test_P1_and_payment_status_utc_naive_now_later(payfip, backend, freezer):
|
||||
now = datetime.datetime.now()
|
||||
freezer.move_to(datetime.timedelta(minutes=25))
|
||||
response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=now)
|
||||
assert response.result == eopayment.EXPIRED
|
||||
|
||||
|
||||
def test_get_info_paiement_P5(payfip):
|
||||
# idop reçu par la plate-forme mais transaction en cours
|
||||
with pytest.raises(PayFiPError, match='.*P5.*sultat de la transaction non connu.*'):
|
||||
payfip.get_info_paiement('cc0cb210-1cd4-11ea-8cca-0213ad91a103')
|
||||
|
||||
|
||||
def test_payment_ok(request):
|
||||
payment = eopayment.Payment('payfip_ws', {
|
||||
'numcli': '090909',
|
||||
'automatic_return_url': NOTIF_URL,
|
||||
'normal_return_url': REDIRECT_URL,
|
||||
})
|
||||
|
||||
with httmock.HTTMock(PayFiPHTTMock(request).mock):
|
||||
payment_id, kind, url = payment.request(
|
||||
amount='10.00',
|
||||
email='john.doe@example.com',
|
||||
# make test deterministic
|
||||
refdet='201912261758460053903194')
|
||||
|
||||
assert payment_id == 'cc0cb210-1cd4-11ea-8cca-0213ad91a103'
|
||||
assert kind == eopayment.URL
|
||||
assert url == 'https://www.tipi.budget.gouv.fr/tpa/paiementws.web?idop=cc0cb210-1cd4-11ea-8cca-0213ad91a103'
|
||||
|
||||
response = payment.response('idop=%s' % payment_id)
|
||||
assert response.result == eopayment.PAID
|
||||
assert response.bank_status == 'paid CB'
|
||||
assert response.order_id == payment_id
|
||||
assert response.transaction_id == (
|
||||
'201912261758460053903194 cc0cb210-1cd4-11ea-8cca-0213ad91a103 112233445566-tip')
|
||||
@set_history_name('test_get_info_paiement_P5')
|
||||
def test_P5_and_payment_status(payfip, backend, freezer):
|
||||
response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103')
|
||||
assert response.result == eopayment.WAITING
|
||||
|
||||
|
||||
def test_payment_denied(request):
|
||||
payment = eopayment.Payment('payfip_ws', {
|
||||
'numcli': '090909',
|
||||
'automatic_return_url': NOTIF_URL,
|
||||
'normal_return_url': REDIRECT_URL,
|
||||
})
|
||||
|
||||
with httmock.HTTMock(PayFiPHTTMock(request).mock):
|
||||
payment_id, kind, url = payment.request(
|
||||
amount='10.00',
|
||||
email='john.doe@example.com',
|
||||
# make test deterministic
|
||||
refdet='201912261758460053903194')
|
||||
|
||||
assert payment_id == 'cc0cb210-1cd4-11ea-8cca-0213ad91a103'
|
||||
assert kind == eopayment.URL
|
||||
assert url == 'https://www.tipi.budget.gouv.fr/tpa/paiementws.web?idop=cc0cb210-1cd4-11ea-8cca-0213ad91a103'
|
||||
|
||||
response = payment.response('idop=%s' % payment_id)
|
||||
assert response.result == eopayment.DENIED
|
||||
assert response.bank_status == 'refused CB'
|
||||
assert response.order_id == payment_id
|
||||
assert response.transaction_id == '201912261758460053903194 cc0cb210-1cd4-11ea-8cca-0213ad91a103'
|
||||
@set_history_name('test_get_info_paiement_P5')
|
||||
def test_P5_and_payment_status_utc_aware_now(payfip, backend, freezer):
|
||||
utc_now = datetime.datetime.now(pytz.utc)
|
||||
response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=utc_now)
|
||||
assert response.result == eopayment.WAITING
|
||||
|
||||
|
||||
def test_payment_cancelled(request):
|
||||
payment = eopayment.Payment('payfip_ws', {
|
||||
'numcli': '090909',
|
||||
'automatic_return_url': NOTIF_URL,
|
||||
'normal_return_url': REDIRECT_URL,
|
||||
})
|
||||
@set_history_name('test_get_info_paiement_P5')
|
||||
def test_P5_and_payment_status_utc_naive_now(payfip, backend, freezer):
|
||||
now = datetime.datetime.now()
|
||||
response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=now)
|
||||
assert response.result == eopayment.WAITING
|
||||
|
||||
with httmock.HTTMock(PayFiPHTTMock(request).mock):
|
||||
payment_id, kind, url = payment.request(
|
||||
amount='10.00',
|
||||
email='john.doe@example.com',
|
||||
# make test deterministic
|
||||
refdet='201912261758460053903194')
|
||||
|
||||
assert payment_id == 'cc0cb210-1cd4-11ea-8cca-0213ad91a103'
|
||||
assert kind == eopayment.URL
|
||||
assert url == 'https://www.tipi.budget.gouv.fr/tpa/paiementws.web?idop=cc0cb210-1cd4-11ea-8cca-0213ad91a103'
|
||||
@set_history_name('test_get_info_paiement_P5')
|
||||
def test_P5_and_payment_status_utc_aware_now_later(payfip, backend, freezer):
|
||||
utc_now = datetime.datetime.now(pytz.utc)
|
||||
freezer.move_to(datetime.timedelta(minutes=25))
|
||||
response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=utc_now)
|
||||
assert response.result == eopayment.EXPIRED
|
||||
|
||||
response = payment.response('idop=%s' % payment_id)
|
||||
assert response.result == eopayment.CANCELLED
|
||||
assert response.bank_status == 'cancelled CB'
|
||||
assert response.order_id == payment_id
|
||||
assert response.transaction_id == '201912261758460053903194 cc0cb210-1cd4-11ea-8cca-0213ad91a103'
|
||||
|
||||
@set_history_name('test_get_info_paiement_P5')
|
||||
def test_P5_and_payment_status_utc_naive_now_later(payfip, backend, freezer):
|
||||
now = datetime.datetime.now()
|
||||
freezer.move_to(datetime.timedelta(minutes=25))
|
||||
response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=now)
|
||||
assert response.result == eopayment.EXPIRED
|
||||
|
||||
|
||||
def test_payment_ok(payfip, backend):
|
||||
payment_id, kind, url = backend.request(
|
||||
amount='10.00',
|
||||
email='john.doe@example.com',
|
||||
# make test deterministic
|
||||
refdet='201912261758460053903194')
|
||||
|
||||
assert payment_id == 'cc0cb210-1cd4-11ea-8cca-0213ad91a103'
|
||||
assert kind == eopayment.URL
|
||||
assert url == 'https://www.tipi.budget.gouv.fr/tpa/paiementws.web?idop=cc0cb210-1cd4-11ea-8cca-0213ad91a103'
|
||||
|
||||
response = backend.response('idop=%s' % payment_id)
|
||||
assert response.result == eopayment.PAID
|
||||
assert response.bank_status == 'paid CB'
|
||||
assert response.order_id == payment_id
|
||||
assert response.transaction_id == (
|
||||
'201912261758460053903194 cc0cb210-1cd4-11ea-8cca-0213ad91a103 112233445566-tip')
|
||||
|
||||
|
||||
@set_history_name('test_payment_ok')
|
||||
def test_payment_status_ok(backend, freezer, history):
|
||||
history.counter = 1 # only the response
|
||||
now = datetime.datetime.now()
|
||||
response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=now)
|
||||
assert response.result == eopayment.PAID
|
||||
|
||||
|
||||
def test_payment_denied(backend):
|
||||
payment_id, kind, url = backend.request(
|
||||
amount='10.00',
|
||||
email='john.doe@example.com',
|
||||
# make test deterministic
|
||||
refdet='201912261758460053903194')
|
||||
|
||||
assert payment_id == 'cc0cb210-1cd4-11ea-8cca-0213ad91a103'
|
||||
assert kind == eopayment.URL
|
||||
assert url == 'https://www.tipi.budget.gouv.fr/tpa/paiementws.web?idop=cc0cb210-1cd4-11ea-8cca-0213ad91a103'
|
||||
|
||||
response = backend.response('idop=%s' % payment_id)
|
||||
assert response.result == eopayment.DENIED
|
||||
assert response.bank_status == 'refused CB'
|
||||
assert response.order_id == payment_id
|
||||
assert response.transaction_id == '201912261758460053903194 cc0cb210-1cd4-11ea-8cca-0213ad91a103'
|
||||
|
||||
|
||||
@set_history_name('test_payment_denied')
|
||||
def test_payment_status_denied(backend, freezer, history):
|
||||
history.counter = 1 # only the response
|
||||
now = datetime.datetime.now()
|
||||
response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=now)
|
||||
assert response.result == eopayment.DENIED
|
||||
|
||||
|
||||
def test_payment_cancelled(backend):
|
||||
payment_id, kind, url = backend.request(
|
||||
amount='10.00',
|
||||
email='john.doe@example.com',
|
||||
# make test deterministic
|
||||
refdet='201912261758460053903194')
|
||||
|
||||
assert payment_id == 'cc0cb210-1cd4-11ea-8cca-0213ad91a103'
|
||||
assert kind == eopayment.URL
|
||||
assert url == 'https://www.tipi.budget.gouv.fr/tpa/paiementws.web?idop=cc0cb210-1cd4-11ea-8cca-0213ad91a103'
|
||||
|
||||
response = backend.response('idop=%s' % payment_id)
|
||||
assert response.result == eopayment.CANCELLED
|
||||
assert response.bank_status == 'cancelled CB'
|
||||
assert response.order_id == payment_id
|
||||
assert response.transaction_id == '201912261758460053903194 cc0cb210-1cd4-11ea-8cca-0213ad91a103'
|
||||
|
||||
|
||||
@set_history_name('test_payment_cancelled')
|
||||
def test_payment_status_cancelled(backend, freezer, history):
|
||||
history.counter = 1 # only the response
|
||||
now = datetime.datetime.now()
|
||||
response = backend.payment_status(transaction_id='cc0cb210-1cd4-11ea-8cca-0213ad91a103', transaction_date=now)
|
||||
assert response.result == eopayment.CANCELLED
|
||||
|
|
Loading…
Reference in New Issue