pass order id to all backends (#9941)
This commit is contained in:
parent
33ba732c91
commit
8cd625e465
|
@ -29,6 +29,15 @@ reported for logging purpose.
|
|||
The spplus and paybox module also depend upon the python Crypto library for DES
|
||||
decoding of the merchant key and RSA signature validation on the responses.
|
||||
|
||||
Some backends allow to specify the order and transaction ids in different
|
||||
fields, in order to allow to match them in payment system backoffice. They are:
|
||||
- Payzen
|
||||
- SIPS
|
||||
- SystemPay
|
||||
|
||||
For other backends, the order and transaction ids, separated by '!' are sent in
|
||||
order id field, so they can be matched in backoffice.
|
||||
|
||||
Changelog
|
||||
=========
|
||||
|
||||
|
|
|
@ -23,6 +23,9 @@ CANCELLED = 5
|
|||
CANCELED = 5 # typo for backward compatibility
|
||||
ERROR = 99
|
||||
|
||||
# separator between order and transaction ids
|
||||
ORDERID_TRANSACTION_SEPARATOR = '!'
|
||||
|
||||
|
||||
class PaymentResponse(object):
|
||||
'''Holds a generic view on the result of payment transaction response.
|
||||
|
|
|
@ -72,7 +72,7 @@ class Payment(PaymentCommon):
|
|||
}
|
||||
|
||||
def request(self, amount, name=None, address=None, email=None, phone=None,
|
||||
info1=None, info2=None, info3=None, next_url=None, **kwargs):
|
||||
orderid=None, info1=None, info2=None, info3=None, next_url=None, **kwargs):
|
||||
self.logger.debug('%s amount %s name %s address %s email %s phone %s'
|
||||
' next_url %s info1 %s info2 %s info3 %s kwargs: %s',
|
||||
__name__, amount, name, address, email, phone, info1, info2, info3, next_url, kwargs)
|
||||
|
@ -89,7 +89,7 @@ class Payment(PaymentCommon):
|
|||
'origin': self.origin
|
||||
}
|
||||
query.update(dict(name=name, address=address, email=email, phone=phone,
|
||||
info1=info1, info2=info2, info3=info3))
|
||||
orderid=orderid, info1=info1, info2=info2, info3=info3))
|
||||
for key in query.keys():
|
||||
if query[key] is None:
|
||||
del query[key]
|
||||
|
@ -128,13 +128,11 @@ if __name__ == '__main__':
|
|||
p = Payment(options)
|
||||
retour = 'http://example.com/retour?amount=10.0&direct_notification_url=http%3A%2F%2Fexample.com%2Fdirect_notification_url&email=toto%40example.com&transaction_id=6Tfw2e1bPyYnz7CedZqvdHt7T9XX6T&return_url=http%3A%2F%2Fexample.com%2Fretour&nok=1'
|
||||
r = p.response(retour.split('?',1)[1])
|
||||
assert not r[0]
|
||||
assert not r[0]
|
||||
assert r[1] == '6Tfw2e1bPyYnz7CedZqvdHt7T9XX6T'
|
||||
assert r[3] is None
|
||||
retour = 'http://example.com/retour?amount=10.0&direct_notification_url=http%3A%2F%2Fexample.com%2Fdirect_notification_url&email=toto%40example.com&transaction_id=6Tfw2e1bPyYnz7CedZqvdHt7T9XX6T&return_url=http%3A%2F%2Fexample.com%2Fretour&ok=1&signed=1'
|
||||
r = p.response(retour.split('?',1)[1])
|
||||
assert r[0]
|
||||
assert r[0]
|
||||
assert r[1] == '6Tfw2e1bPyYnz7CedZqvdHt7T9XX6T'
|
||||
assert r[3] == 'signature ok'
|
||||
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ import urlparse
|
|||
from decimal import Decimal, ROUND_HALF_UP
|
||||
|
||||
from common import (PaymentCommon, PaymentResponse, FORM, CANCELLED, PAID,
|
||||
ERROR, Form, DENIED, ACCEPTED)
|
||||
ERROR, Form, DENIED, ACCEPTED, ORDERID_TRANSACTION_SEPARATOR)
|
||||
def N_(message): return message
|
||||
|
||||
ENVIRONMENT_TEST = 'TEST'
|
||||
|
@ -467,7 +467,14 @@ class Payment(PaymentCommon):
|
|||
|
||||
def request(self, amount, orderid=None, name=None, email=None,
|
||||
language=None, description=None, **kwargs):
|
||||
reference = orderid or self.transaction_id(20, string.digits + string.ascii_letters)
|
||||
|
||||
reference = self.transaction_id(20, string.digits + string.ascii_letters)
|
||||
|
||||
# prepend order id in payment reference
|
||||
if orderid:
|
||||
if len(orderid) > 24:
|
||||
raise ValueError('orderid length exceeds 25 characters')
|
||||
reference = orderid + ORDERID_TRANSACTION_SEPARATOR + self.transaction_id(29-len(orderid), string.digits + string.ascii_letters)
|
||||
language = language or self.language
|
||||
# convertir en centimes
|
||||
amount = Decimal(amount) * 100
|
||||
|
@ -527,6 +534,9 @@ class Payment(PaymentCommon):
|
|||
self.logger.error('response STATUS=%s NCERROR=%s NCERRORPLUS=%s',
|
||||
status, error, params.get('NCERRORPLUS', ''))
|
||||
result = ERROR
|
||||
# extract reference from received order id
|
||||
if ORDERID_TRANSACTION_SEPARATOR in reference:
|
||||
reference, transaction_id = reference.split(ORDERID_TRANSACTION_SEPARATOR, 1)
|
||||
return PaymentResponse(
|
||||
result=result,
|
||||
signed=signed,
|
||||
|
|
|
@ -15,7 +15,8 @@ import base64
|
|||
from gettext import gettext as _
|
||||
import string
|
||||
|
||||
from common import PaymentCommon, PaymentResponse, FORM, PAID, ERROR, Form
|
||||
from common import (PaymentCommon, PaymentResponse, FORM, PAID, ERROR, Form,
|
||||
ORDERID_TRANSACTION_SEPARATOR)
|
||||
|
||||
__all__ = ['sign', 'Payment']
|
||||
|
||||
|
@ -192,7 +193,7 @@ class Payment(PaymentCommon):
|
|||
]
|
||||
}
|
||||
|
||||
def request(self, amount, email, name=None, **kwargs):
|
||||
def request(self, amount, email, name=None, orderid=None, **kwargs):
|
||||
d = OrderedDict()
|
||||
d['PBX_SITE'] = unicode(self.site)
|
||||
d['PBX_RANG'] = unicode(self.rang).strip()[-2:]
|
||||
|
@ -203,6 +204,9 @@ class Payment(PaymentCommon):
|
|||
self.transaction_id(12, string.digits, 'paybox', self.site,
|
||||
self.rang, self.identifiant)
|
||||
d['PBX_CMD'] = unicode(transaction_id)
|
||||
# prepend order id command reference
|
||||
if orderid:
|
||||
d['PBX_CMD'] = orderid + ORDERID_TRANSACTION_SEPARATOR + d['PBX_CMD']
|
||||
d['PBX_PORTEUR'] = unicode(email)
|
||||
d['PBX_RETOUR'] = 'montant:M;reference:R;code_autorisation:A;erreur:E;signature:K'
|
||||
d['PBX_HASH'] = 'SHA512'
|
||||
|
@ -252,9 +256,12 @@ class Payment(PaymentCommon):
|
|||
bank_status = PAYBOX_ERROR_CODES.get(prefix + suffix)
|
||||
if bank_status is not None:
|
||||
break
|
||||
|
||||
orderid = d['reference'][0]
|
||||
# decode order id from returned reference
|
||||
if ORDERID_TRANSACTION_SEPARATOR in orderid:
|
||||
orderid, transaction_id = orderid.split(ORDERID_TRANSACTION_SEPARATOR, 1)
|
||||
return PaymentResponse(
|
||||
order_id=d['reference'][0],
|
||||
order_id=orderid,
|
||||
signed=signed,
|
||||
bank_data=d,
|
||||
result=result,
|
||||
|
|
|
@ -132,13 +132,14 @@ class Payment(PaymentCommon):
|
|||
params.update(self.options)
|
||||
return params
|
||||
|
||||
def request(self, amount, name=None, address=None, email=None, phone=None, info1=None,
|
||||
info2=None, info3=None, next_url=None, **kwargs):
|
||||
def request(self, amount, name=None, address=None, email=None, phone=None, orderid=None,
|
||||
info1=None, info2=None, info3=None, next_url=None, **kwargs):
|
||||
params = self.get_request_params()
|
||||
transaction_id = self.transaction_id(6, string.digits, 'sips',
|
||||
params[MERCHANT_ID])
|
||||
params[TRANSACTION_ID] = transaction_id
|
||||
params[ORDER_ID] = str(uuid.uuid4()).replace('-', '')
|
||||
params[ORDER_ID] = orderid or str(uuid.uuid4())
|
||||
params[ORDER_ID] = params[ORDER_ID].replace('-', '')
|
||||
params['amount'] = str(int(Decimal(amount) * 100))
|
||||
if email:
|
||||
params['customer_email'] = email
|
||||
|
|
|
@ -135,12 +135,12 @@ class Payment(PaymentCommon):
|
|||
def get_url(self):
|
||||
return self.URL[self.platform]
|
||||
|
||||
def request(self, amount, name=None, address=None, email=None, phone=None, info1=None,
|
||||
info2=None, info3=None, next_url=None, **kwargs):
|
||||
def request(self, amount, name=None, address=None, email=None, phone=None,
|
||||
orderid=None, info1=None, info2=None, info3=None, next_url=None, **kwargs):
|
||||
data = self.get_data()
|
||||
transaction_id = self.transaction_id(6, string.digits, 'sips2', data['merchantId'])
|
||||
data['transactionReference'] = unicode(transaction_id)
|
||||
data['orderId'] = unicode(uuid.uuid4()).replace('-', '')
|
||||
data['orderId'] = orderid or unicode(uuid.uuid4()).replace('-', '')
|
||||
data['amount'] = unicode(int(Decimal(amount) * 100))
|
||||
if email:
|
||||
data['billingContact.email'] = email
|
||||
|
|
|
@ -122,8 +122,9 @@ class Payment(PaymentCommon):
|
|||
}
|
||||
devise = '978'
|
||||
|
||||
def request(self, amount, name=None, address=None, email=None, phone=None, info1=None,
|
||||
info2=None, info3=None, next_url=None, logger=LOGGER, **kwargs):
|
||||
def request(self, amount, name=None, address=None, email=None, phone=None,
|
||||
orderid=None, info1=None, info2=None, info3=None, next_url=None,
|
||||
logger=LOGGER, **kwargs):
|
||||
logger.debug('requesting spplus payment with montant %s email=%s and \
|
||||
next_url=%s' % (amount, email, next_url))
|
||||
reference = self.transaction_id(20, ALPHANUM, 'spplus', self.siret)
|
||||
|
@ -134,7 +135,7 @@ next_url=%s' % (amount, email, next_url))
|
|||
'langue': self.langue,
|
||||
'taxe': self.taxe,
|
||||
'montant': str(Decimal(amount)),
|
||||
REFERENCE: reference,
|
||||
REFERENCE: orderid or reference,
|
||||
'validite': validite,
|
||||
'version': '1',
|
||||
'modalite': self.modalite,
|
||||
|
|
|
@ -252,7 +252,8 @@ class Payment(PaymentCommon):
|
|||
self.logger = logger or logging.getLogger(__name__)
|
||||
|
||||
def request(self, amount, name=None, address=None, email=None, phone=None,
|
||||
info1=None, info2=None, info3=None, next_url=None, **kwargs):
|
||||
orderid=None, info1=None, info2=None, info3=None,
|
||||
next_url=None, **kwargs):
|
||||
'''
|
||||
Create the URL string to send a request to SystemPay
|
||||
'''
|
||||
|
@ -281,6 +282,16 @@ class Payment(PaymentCommon):
|
|||
kwargs['vads_order_info2'] = unicode(info2)
|
||||
if info3 is not None:
|
||||
kwargs['vads_order_info3'] = unicode(info3)
|
||||
if orderid is not None:
|
||||
# check orderid format first
|
||||
name = 'vads_order_id'
|
||||
orderid = unicode(orderid)
|
||||
ptype = 'an-'
|
||||
p = Parameter(name, ptype, 13, max_length=32)
|
||||
if not p.check_value(orderid):
|
||||
raise ValueError('%s value %s is not of the type %s' % (name,
|
||||
orderid, ptype))
|
||||
kwargs[name] = orderid
|
||||
|
||||
transaction_id = self.transaction_id(6, string.digits, 'systempay',
|
||||
self.options[VADS_SITE_ID])
|
||||
|
|
|
@ -48,8 +48,8 @@ class Payment(PaymentCommon):
|
|||
self.numcli = options.pop('numcli', '')
|
||||
self.logger = logger
|
||||
|
||||
def request(self, amount, next_url=None, exer=None, refdet=None,
|
||||
objet=None, email=None, saisie=None, **kwargs):
|
||||
def request(self, amount, next_url=None, exer=None, orderid=None,
|
||||
refdet=None, objet=None, email=None, saisie=None, **kwargs):
|
||||
try:
|
||||
montant = Decimal(amount)
|
||||
if Decimal('0') > montant > Decimal('9999.99'):
|
||||
|
@ -73,6 +73,7 @@ class Payment(PaymentCommon):
|
|||
except ValueError:
|
||||
raise ValueError('EXER format invalide')
|
||||
try:
|
||||
refdet = orderid or refdet
|
||||
refdet = str(refdet)
|
||||
if 6 > len(refdet) > 30:
|
||||
raise ValueError('len(REFDET) < 6 or > 30')
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
from unittest import TestCase
|
||||
import urllib
|
||||
|
||||
import eopayment
|
||||
import eopayment.ogone as ogone
|
||||
|
||||
PSPID = '2352566'
|
||||
|
||||
BACKEND_PARAMS = {
|
||||
'environment': ogone.ENVIRONMENT_TEST,
|
||||
'pspid': PSPID,
|
||||
'sha_in': 'secret',
|
||||
'sha_out': 'secret'
|
||||
}
|
||||
|
||||
class OgoneTests(TestCase):
|
||||
|
||||
def test_request(self):
|
||||
ogone_backend = eopayment.Payment('ogone', BACKEND_PARAMS)
|
||||
amount = '42.42'
|
||||
order_id = 'myorder'
|
||||
reference, kind, what = ogone_backend.request(amount=amount,
|
||||
orderid=order_id, email='foo@example.com')
|
||||
self.assertEqual(len(reference), 30)
|
||||
assert reference.startswith(order_id)
|
||||
from xml.etree import ElementTree as ET
|
||||
root = ET.fromstring(str(what))
|
||||
self.assertEqual(root.tag, 'form')
|
||||
self.assertEqual(root.attrib['method'], 'POST')
|
||||
self.assertEqual(root.attrib['action'], ogone.ENVIRONMENT_TEST_URL)
|
||||
values = {
|
||||
'CURRENCY': 'EUR',
|
||||
'ORDERID': reference,
|
||||
'PSPID': PSPID,
|
||||
'EMAIL': 'foo@example.com',
|
||||
'AMOUNT': amount.replace('.', ''),
|
||||
'LANGUAGE': 'fr_FR',
|
||||
}
|
||||
values.update({'SHASIGN': ogone_backend.backend.sha_sign_in(values)})
|
||||
for node in root:
|
||||
self.assertIn(node.attrib['type'], ('hidden', 'submit'))
|
||||
self.assertEqual(set(node.attrib.keys()), set(['type', 'name', 'value']))
|
||||
name = node.attrib['name']
|
||||
if node.attrib['type'] == 'hidden':
|
||||
self.assertIn(name, values)
|
||||
self.assertEqual(node.attrib['value'], values[name])
|
||||
|
||||
def test_response(self):
|
||||
ogone_backend = eopayment.Payment('ogone', BACKEND_PARAMS)
|
||||
order_id = 'myorder'
|
||||
data = {'orderid': order_id + eopayment.common.ORDERID_TRANSACTION_SEPARATOR + 'RtEpMXZn4dX8k1rYbwLlby',
|
||||
'payid': '32100123', 'status': 9, 'ncerror': 0}
|
||||
response = ogone_backend.response(urllib.urlencode(data))
|
||||
self.assertEqual(response.order_id, order_id)
|
|
@ -1,18 +1,28 @@
|
|||
from unittest import TestCase
|
||||
from decimal import Decimal
|
||||
import base64
|
||||
import urllib
|
||||
|
||||
import eopayment.paybox as paybox
|
||||
import eopayment
|
||||
|
||||
BACKEND_PARAMS = {
|
||||
'platform': 'test',
|
||||
'site': '12345678',
|
||||
'rang': '001',
|
||||
'identifiant': '12345678',
|
||||
'shared_secret': '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF',
|
||||
'callback': 'http://example.com/callback',
|
||||
}
|
||||
|
||||
class PayboxTests(TestCase):
|
||||
def test_sign(self):
|
||||
key = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'.decode('hex')
|
||||
d = dict(paybox.sign([
|
||||
['PBX_SITE', '1999888'],
|
||||
['PBX_SITE', '12345678'],
|
||||
['PBX_RANG', '32'],
|
||||
['PBX_IDENTIFIANT', '110647233'],
|
||||
['PBX_TOTAL', '999'],
|
||||
['PBX_IDENTIFIANT', '12345678'],
|
||||
['PBX_TOTAL', '999'],
|
||||
['PBX_DEVISE', '978'],
|
||||
['PBX_CMD', 'TEST Paybox'],
|
||||
['PBX_PORTEUR', 'test@paybox.com'],
|
||||
|
@ -21,25 +31,20 @@ class PayboxTests(TestCase):
|
|||
['PBX_TIME', '2015-06-08T16:21:16+02:00'],
|
||||
],
|
||||
key))
|
||||
result = '7ABB5F7A31DF4C8976A44374D3BA2F9831E7927CFD62F774ED378F4E27471708F4EFE6D0BEFA44EBABCBD978B661C74E22EEB16DEF73A510E86D0A5C0E7B6D88'
|
||||
result = '475FE1C45A0D09D26D2CAC9A1AC39C024773D790F50B6DD15B260F55FCD527FD3AD4AA3998F4162EFE9BDC494B9850A673355A32ACC4F85B67F8566037836F8E'
|
||||
self.assertIn('PBX_HMAC', d)
|
||||
self.assertEqual(d['PBX_HMAC'], result)
|
||||
|
||||
def test_request(self):
|
||||
key = '0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF'
|
||||
backend = eopayment.Payment('paybox', {
|
||||
'platform': 'test',
|
||||
'site': '12345678',
|
||||
'rang': '001',
|
||||
'identifiant': '12345678',
|
||||
'shared_secret': key,
|
||||
'callback': 'http://example.com/callback',
|
||||
})
|
||||
backend = eopayment.Payment('paybox', BACKEND_PARAMS)
|
||||
time = '2015-07-15T18:26:32+02:00'
|
||||
email = 'bdauvergne@entrouvert.com'
|
||||
order_id = '20160216'
|
||||
transaction = '1234'
|
||||
amount = '19.99'
|
||||
transaction_id, kind, what = backend.request(
|
||||
Decimal('19.99'), email=email,
|
||||
transaction_id='1234', time=time)
|
||||
Decimal(amount), email=email, orderid=order_id,
|
||||
transaction_id=transaction, time=time)
|
||||
self.assertEqual(kind, eopayment.FORM)
|
||||
self.assertEqual(transaction_id, '1234')
|
||||
from xml.etree import ElementTree as ET
|
||||
|
@ -54,6 +59,7 @@ class PayboxTests(TestCase):
|
|||
if node.attrib['type'] == 'hidden':
|
||||
self.assertEqual(set(node.attrib.keys()), set(['type', 'name', 'value']))
|
||||
name = node.attrib['name']
|
||||
reference = order_id + eopayment.common.ORDERID_TRANSACTION_SEPARATOR + transaction
|
||||
values = {
|
||||
'PBX_RANG': '01',
|
||||
'PBX_SITE': '12345678',
|
||||
|
@ -61,17 +67,27 @@ class PayboxTests(TestCase):
|
|||
'PBX_RETOUR': 'montant:M;reference:R;code_autorisation:A;erreur:E;signature:K',
|
||||
'PBX_TIME': time,
|
||||
'PBX_PORTEUR': email,
|
||||
'PBX_CMD': '1234',
|
||||
'PBX_TOTAL': '1999',
|
||||
'PBX_CMD': reference,
|
||||
'PBX_TOTAL': amount.replace('.', ''),
|
||||
'PBX_DEVISE': '978',
|
||||
'PBX_HASH': 'SHA512',
|
||||
'PBX_HMAC': 'A0AA37FC3DD46F3233C0AD3BF95242CD71003D98F33DF85124E4423D53759A82A132EC2CC42B7234B22A75F00CF5DA124DF3A34331F3F6B9D7308B2EF09DCA3C',
|
||||
'PBX_HMAC': '173483CFF84A7ECF21039F99E9A95C5FB53D98A1562184F5B2C4543E4F87BFA227CC2CA10DE989D6C8B4DC03BC2ED44B7D7BDF5B4FABA8274D5D37C2F6445F36',
|
||||
'PBX_ARCHIVAGE': '1234',
|
||||
'PBX_REPONDRE_A': 'http://example.com/callback',
|
||||
}
|
||||
self.assertIn(name, values)
|
||||
self.assertEqual(node.attrib['value'], values[name])
|
||||
|
||||
def test_response(self):
|
||||
backend = eopayment.Payment('paybox', BACKEND_PARAMS)
|
||||
order_id = '20160216'
|
||||
transaction = '1234'
|
||||
reference = order_id + eopayment.common.ORDERID_TRANSACTION_SEPARATOR + transaction
|
||||
data = {'montant': '4242', 'reference': reference,
|
||||
'code_autorisation': 'A', 'erreur': '00000'}
|
||||
response = backend.response(urllib.urlencode(data))
|
||||
self.assertEqual(response.order_id, order_id)
|
||||
|
||||
def test_rsa_signature_validation(self):
|
||||
pkey = '''-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDUgYufHuheMztK1LhQSG6xsOzb
|
||||
|
|
Loading…
Reference in New Issue