paybox: implement transaction validation and cancelling (#26960)

This commit is contained in:
Serghei Mihai 2018-10-09 16:43:41 +02:00
parent 383109078d
commit 18eef0578f
3 changed files with 126 additions and 0 deletions

View File

@ -6,6 +6,7 @@ import datetime
import logging
import hashlib
import hmac
import requests
from decimal import Decimal, ROUND_DOWN
from Crypto.Signature import PKCS1_v1_5
from Crypto.PublicKey import RSA
@ -99,6 +100,19 @@ URLS = {
'https://tpeweb1.paybox.com/cgi/MYchoix_pagepaiement.cgi',
}
PAYBOX_DIRECT_URLS = {
'test': 'https://preprod-ppps.paybox.com/PPPS.php',
'prod': 'https://ppps.paybox.com/PPPS.php',
'backup': 'https://ppps1.paybox.com/PPPS.php'
}
PAYBOX_DIRECT_CANCEL_OPERATION = '00005'
PAYBOX_DIRECT_VALIDATE_OPERATION = '00002'
PAYBOX_DIRECT_VERSION_NUMBER = '00104'
PAYBOX_DIRECT_SUCCESS_RESPONSE_CODE = '00000'
def sign(data, key):
'''Take a list of tuple key, value and sign it by building a string to
@ -174,6 +188,12 @@ class Payment(PaymentCommon):
'validation': lambda x: isinstance(x, basestring) and
x.isdigit() and len(x) == 7,
},
{
'name': 'cle',
'caption': _('Site key'),
'required': False,
'validation': lambda x: isinstance(x, basestring),
},
{
'name': 'rang',
'caption': _('Numéro de rang'),
@ -311,3 +331,33 @@ class Payment(PaymentCommon):
bank_data=d,
result=result,
bank_status=bank_status)
def perform(self, amount, bank_data, operation):
logger = logging.getLogger(__name__)
url = PAYBOX_DIRECT_URLS[self.platform]
params = {'VERSION': PAYBOX_DIRECT_VERSION_NUMBER,
'TYPE': operation,
'SITE': force_text(self.site),
'RANG': self.rang.strip(),
'CLE': force_text(self.cle),
'NUMQUESTION': bank_data['numero_transaction'][0].zfill(10),
'MONTANT': (amount * Decimal(100)).to_integral_value(ROUND_DOWN),
'DEVISE': force_text(self.devise),
'NUMTRANS': bank_data['numero_transaction'][0], # paybox transaction number
'NUMAPPEL': bank_data['numero_appel'][0],
'REFERENCE': bank_data['reference'][0],
'DATEQ': datetime.datetime.now().strftime('%d%m%Y%H%M%S'),
}
response = requests.post(url, params)
response.raise_for_status()
logger.debug('received %r', response.content)
data = dict(urlparse.parse_qsl(response.content, True, True))
if data.get('CODEREPONSE') != PAYBOX_DIRECT_SUCCESS_RESPONSE_CODE:
raise ResponseError(data.get('COMMENTAIRE'))
return data
def validate(self, amount, bank_data, **kwargs):
return self.perform(amount, bank_data, PAYBOX_DIRECT_VALIDATE_OPERATION)
def cancel(self, amount, bank_data, **kwargs):
return self.perform(amount, bank_data, PAYBOX_DIRECT_CANCEL_OPERATION)

View File

@ -4,6 +4,7 @@ import codecs
from unittest import TestCase
from decimal import Decimal
import base64
import mock
from six.moves.urllib import parse as urllib
from xml.etree import ElementTree as ET
@ -116,6 +117,80 @@ class PayboxTests(TestCase):
with self.assertRaisesRegexp(eopayment.ResponseError, 'missing erreur or reference'):
backend.response('foo=bar')
def test_perform_operations(self):
operations = {'validate': '00002', 'cancel': '00005'}
for operation_name, operation_code in operations.items():
params = BACKEND_PARAMS.copy()
params['cle'] = 'cancelling_key'
backend = eopayment.Payment('paybox', params)
bank_data = {'numero_transaction': ['13957441'],
'numero_appel': ['30310733'],
'reference': ['830657461681']
}
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="""
backend_expected_response = {"CODEREPONSE": "00000",
"RANG": "32",
"AUTORISATION": "XXXXXX",
"NUMTRANS": "0013989865",
"PORTEUR": "",
"COMMENTAIRE": "Demande traitée avec succès",
"SITE": "1999888",
"NUMAPPEL": "0030378572",
"REFABONNE": "",
"NUMQUESTION": "0013989862"}
with mock.patch('eopayment.paybox.requests.post') as requests_post:
response = mock.Mock(status_code=200, content=backend_raw_response)
requests_post.return_value = response
backend_response = getattr(backend, operation_name)(Decimal('10'), bank_data)
self.assertEqual(requests_post.call_args[0][0], 'https://preprod-ppps.paybox.com/PPPS.php')
params_sent = requests_post.call_args[0][1]
# make sure the date parameter is present
assert 'DATEQ' in params_sent
# don't care about its value
params_sent.pop('DATEQ')
expected_params = {'CLE': 'cancelling_key',
'VERSION': '00104',
'TYPE': operation_code,
'MONTANT': Decimal('1000'),
'NUMAPPEL': '30310733',
'NUMTRANS': '13957441',
'NUMQUESTION': '0013957441',
'REFERENCE': '830657461681',
'RANG': backend.backend.rang,
'SITE': backend.backend.site,
'DEVISE': backend.backend.devise
}
self.assertEqual(params_sent, expected_params)
self.assertEqual(backend_response, backend_expected_response)
params['platform'] = 'prod'
backend = eopayment.Payment('paybox', params)
with mock.patch('eopayment.paybox.requests.post') as requests_post:
response = mock.Mock(status_code=200, content=backend_raw_response)
requests_post.return_value = response
getattr(backend, operation_name)(Decimal('10'), bank_data)
self.assertEqual(requests_post.call_args[0][0], 'https://ppps.paybox.com/PPPS.php')
with mock.patch('eopayment.paybox.requests.post') as requests_post:
error_response = """CODEREPONSE=00015&COMMENTAIRE=PAYBOX : Transaction non trouvée"""
response = mock.Mock(status_code=200, content=error_response)
requests_post.return_value = response
self.assertRaisesRegexp(eopayment.ResponseError, 'Transaction non trouvée', getattr(backend, operation_name),
Decimal('10'), bank_data)
def test_validate_payment(self):
params = BACKEND_PARAMS.copy()
params['cle'] = 'cancelling_key'
backend = eopayment.Payment('paybox', params)
bank_data = {'numero_transaction': ['13957441'],
'numero_appel': ['30310733'],
'reference': ['830657461681']
}
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="""
def test_rsa_signature_validation(self):
pkey = '''-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDUgYufHuheMztK1LhQSG6xsOzb

View File

@ -17,3 +17,4 @@ usedevelop = True
deps = coverage
pytest
py2: pytest-cov
mock