0001-paybox-implement-transaction-validation-and-cancelli.patch
eopayment/paybox.py | ||
---|---|---|
6 | 6 |
import logging |
7 | 7 |
import hashlib |
8 | 8 |
import hmac |
9 |
import requests |
|
9 | 10 |
from decimal import Decimal, ROUND_DOWN |
10 | 11 |
from Crypto.Signature import PKCS1_v1_5 |
11 | 12 |
from Crypto.PublicKey import RSA |
... | ... | |
99 | 100 |
'https://tpeweb1.paybox.com/cgi/MYchoix_pagepaiement.cgi', |
100 | 101 |
} |
101 | 102 | |
103 |
PAYBOX_DIRECT_URLS = { |
|
104 |
'test': 'https://preprod-ppps.paybox.com/PPPS.php', |
|
105 |
'prod': 'https://ppps.paybox.com/PPPS.php', |
|
106 |
'backup': 'https://ppps1.paybox.com/PPPS.php' |
|
107 |
} |
|
108 | ||
109 |
PAYBOX_DIRECT_CANCEL_OPERATION = '00005' |
|
110 |
PAYBOX_DIRECT_VALIDATE_OPERATION = '00002' |
|
111 | ||
102 | 112 | |
103 | 113 |
def sign(data, key): |
104 | 114 |
'''Take a list of tuple key, value and sign it by building a string to |
... | ... | |
174 | 184 |
'validation': lambda x: isinstance(x, basestring) and |
175 | 185 |
x.isdigit() and len(x) == 7, |
176 | 186 |
}, |
187 |
{ |
|
188 |
'name': 'cle', |
|
189 |
'caption': _('Site key'), |
|
190 |
'required': False, |
|
191 |
'validation': lambda x: isinstance(x, basestring), |
|
192 |
}, |
|
177 | 193 |
{ |
178 | 194 |
'name': 'rang', |
179 | 195 |
'caption': _('Numéro de rang'), |
... | ... | |
311 | 327 |
bank_data=d, |
312 | 328 |
result=result, |
313 | 329 |
bank_status=bank_status) |
330 | ||
331 |
def perform(self, amount, bank_data, operation): |
|
332 |
logger = logging.getLogger(__name__) |
|
333 |
url = PAYBOX_DIRECT_URLS[self.platform] |
|
334 |
params = {'VERSION': '00104', # protocol version |
|
335 |
'TYPE': operation, |
|
336 |
'SITE': force_text(self.site), |
|
337 |
'RANG': self.rang.strip(), |
|
338 |
'CLE': force_text(self.cle), |
|
339 |
'NUMQUESTION': bank_data['numero_transaction'][0].zfill(10), |
|
340 |
'MONTANT': (amount * Decimal(100)).to_integral_value(ROUND_DOWN), |
|
341 |
'DEVISE': force_text(self.devise), |
|
342 |
'NUMTRANS': bank_data['numero_transaction'][0], # paybox transaction number |
|
343 |
'NUMAPPEL': bank_data['numero_appel'][0], |
|
344 |
'REFERENCE': bank_data['reference'][0], |
|
345 |
'DATEQ': datetime.datetime.now().strftime('%d%m%Y%H%M%S'), |
|
346 |
} |
|
347 |
response = requests.post(url, params) |
|
348 |
response.raise_for_status() |
|
349 |
logger.debug('received %r', response.content) |
|
350 |
data = dict(urlparse.parse_qsl(response.content, True, True)) |
|
351 |
if data.get('CODEREPONSE') != '00000': |
|
352 |
raise ResponseError(data.get('COMMENTAIRE')) |
|
353 |
return data |
|
354 | ||
355 |
def validate(self, amount, bank_data, **kwargs): |
|
356 |
return self.perform(amount, bank_data, PAYBOX_DIRECT_VALIDATE_OPERATION) |
|
357 | ||
358 |
def cancel(self, amount, bank_data, **kwargs): |
|
359 |
return self.perform(amount, bank_data, PAYBOX_DIRECT_CANCEL_OPERATION) |
tests/test_paybox.py | ||
---|---|---|
4 | 4 |
from unittest import TestCase |
5 | 5 |
from decimal import Decimal |
6 | 6 |
import base64 |
7 |
import mock |
|
7 | 8 |
from six.moves.urllib import parse as urllib |
8 | 9 |
from xml.etree import ElementTree as ET |
9 | 10 | |
... | ... | |
116 | 117 |
with self.assertRaisesRegexp(eopayment.ResponseError, 'missing erreur or reference'): |
117 | 118 |
backend.response('foo=bar') |
118 | 119 | |
120 |
def test_perform_operations(self): |
|
121 |
operations = {'validate': '00002', 'cancel': '00005'} |
|
122 |
for operation_name, operation_code in operations.items(): |
|
123 |
params = BACKEND_PARAMS.copy() |
|
124 |
params['cle'] = 'cancelling_key' |
|
125 |
backend = eopayment.Payment('paybox', params) |
|
126 |
bank_data = {'numero_transaction': ['13957441'], |
|
127 |
'numero_appel': ['30310733'], |
|
128 |
'reference': ['830657461681'] |
|
129 |
} |
|
130 |
backend_raw_response = """NUMTRANS=0013989865&NUMAPPEL=0030378572&NUMQUESTION=0013989862&SITE=1999888&RANG=32&AUTORISATION=XXXXXX&CODEREPONSE=00000&COMMENTAIRE=Demande traitée avec succès&REFABONNE=&PORTEUR=""" |
|
131 |
backend_expected_response = {"CODEREPONSE": "00000", |
|
132 |
"RANG": "32", |
|
133 |
"AUTORISATION": "XXXXXX", |
|
134 |
"NUMTRANS": "0013989865", |
|
135 |
"PORTEUR": "", |
|
136 |
"COMMENTAIRE": "Demande traitée avec succès", |
|
137 |
"SITE": "1999888", |
|
138 |
"NUMAPPEL": "0030378572", |
|
139 |
"REFABONNE": "", |
|
140 |
"NUMQUESTION": "0013989862"} |
|
141 | ||
142 |
with mock.patch('eopayment.paybox.requests.post') as requests_post: |
|
143 |
response = mock.Mock(status_code=200, content=backend_raw_response) |
|
144 |
requests_post.return_value = response |
|
145 |
backend_response = getattr(backend, operation_name)(Decimal('10'), bank_data) |
|
146 |
self.assertEqual(requests_post.call_args[0][0], 'https://preprod-ppps.paybox.com/PPPS.php') |
|
147 |
params_sent = requests_post.call_args[0][1] |
|
148 |
# make sure the date parameter is present |
|
149 |
assert 'DATEQ' in params_sent |
|
150 |
# don't care about its value |
|
151 |
params_sent.pop('DATEQ') |
|
152 |
expected_params = {'CLE': 'cancelling_key', |
|
153 |
'VERSION': '00104', |
|
154 |
'TYPE': operation_code, |
|
155 |
'MONTANT': Decimal('1000'), |
|
156 |
'NUMAPPEL': '30310733', |
|
157 |
'NUMTRANS': '13957441', |
|
158 |
'NUMQUESTION': '0013957441', |
|
159 |
'REFERENCE': '830657461681', |
|
160 |
'RANG': backend.backend.rang, |
|
161 |
'SITE': backend.backend.site, |
|
162 |
'DEVISE': backend.backend.devise |
|
163 |
} |
|
164 |
self.assertEqual(params_sent, expected_params) |
|
165 |
self.assertEqual(backend_response, backend_expected_response) |
|
166 | ||
167 |
params['platform'] = 'prod' |
|
168 |
backend = eopayment.Payment('paybox', params) |
|
169 |
with mock.patch('eopayment.paybox.requests.post') as requests_post: |
|
170 |
response = mock.Mock(status_code=200, content=backend_raw_response) |
|
171 |
requests_post.return_value = response |
|
172 |
getattr(backend, operation_name)(Decimal('10'), bank_data) |
|
173 |
self.assertEqual(requests_post.call_args[0][0], 'https://ppps.paybox.com/PPPS.php') |
|
174 | ||
175 |
with mock.patch('eopayment.paybox.requests.post') as requests_post: |
|
176 |
error_response = """CODEREPONSE=00015&COMMENTAIRE=PAYBOX : Transaction non trouvée""" |
|
177 |
response = mock.Mock(status_code=200, content=error_response) |
|
178 |
requests_post.return_value = response |
|
179 |
self.assertRaisesRegexp(eopayment.ResponseError, 'Transaction non trouvée', getattr(backend, operation_name), |
|
180 |
Decimal('10'), bank_data) |
|
181 | ||
182 | ||
183 |
def test_validate_payment(self): |
|
184 |
params = BACKEND_PARAMS.copy() |
|
185 |
params['cle'] = 'cancelling_key' |
|
186 |
backend = eopayment.Payment('paybox', params) |
|
187 |
bank_data = {'numero_transaction': ['13957441'], |
|
188 |
'numero_appel': ['30310733'], |
|
189 |
'reference': ['830657461681'] |
|
190 |
} |
|
191 |
backend_raw_response = """NUMTRANS=0013989865&NUMAPPEL=0030378572&NUMQUESTION=0013989862&SITE=1999888&RANG=32&AUTORISATION=XXXXXX&CODEREPONSE=00000&COMMENTAIRE=Demande traitée avec succès&REFABONNE=&PORTEUR=""" |
|
192 | ||
193 | ||
119 | 194 |
def test_rsa_signature_validation(self): |
120 | 195 |
pkey = '''-----BEGIN PUBLIC KEY----- |
121 | 196 |
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDUgYufHuheMztK1LhQSG6xsOzb |
tox.ini | ||
---|---|---|
15 | 15 |
deps = coverage |
16 | 16 |
pytest |
17 | 17 |
pytest-cov |
18 |
mock |
|
18 |
- |