add sips2 backend (#9824)
It implements the interface version 2.3 of SIPS.
This commit is contained in:
parent
6990c40857
commit
1d659b1b21
18
README.txt
18
README.txt
|
@ -2,7 +2,7 @@ Python module to interface with French's bank online credit card processing
|
|||
services.
|
||||
|
||||
Services supported are:
|
||||
- ATOS/SIP used by:
|
||||
- ATOS/SIPS used by:
|
||||
- BNP under the name Mercanet,
|
||||
- Banque Populaire (before 2010/2011) under the name Cyberplus,
|
||||
- CCF under the name Elysnet,
|
||||
|
@ -25,10 +25,16 @@ from those services, reporting whether the transaction was successful and which
|
|||
one it was. The full content (which is specific to the service) is also
|
||||
reported for logging purpose.
|
||||
|
||||
For SystemPay and SPPlus the module is totally independent from the respective
|
||||
implementation distributed by the Bank, but for ATOS/SIPS the kit distributed
|
||||
by the bank is also needed as the protocol created by ATOS is proprietary and
|
||||
not documented.
|
||||
|
||||
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.
|
||||
|
||||
Changelog
|
||||
=========
|
||||
|
||||
1.4
|
||||
---
|
||||
- add sips2 backend to conform with version 2.3 of their interface
|
||||
|
||||
1.3
|
||||
---
|
||||
- add payzen backend
|
||||
|
|
|
@ -7,6 +7,7 @@ __all__ = ['Payment', 'URL', 'HTML', 'FORM', 'SIPS',
|
|||
'PAID', 'DENIED', 'CANCELED', 'CANCELLED', 'ERROR', 'get_backends']
|
||||
|
||||
SIPS = 'sips'
|
||||
SIPS2 = 'sips2'
|
||||
SYSTEMPAY = 'systempayv2'
|
||||
SPPLUS = 'spplus'
|
||||
TIPI = 'tipi'
|
||||
|
@ -21,7 +22,7 @@ def get_backend(kind):
|
|||
module = __import__(kind, globals(), locals(), [])
|
||||
return module.Payment
|
||||
|
||||
__BACKENDS = [ DUMMY, SIPS, SYSTEMPAY, SPPLUS, OGONE, PAYBOX, PAYZEN ]
|
||||
__BACKENDS = [ DUMMY, SIPS, SIPS2, SYSTEMPAY, SPPLUS, OGONE, PAYBOX, PAYZEN ]
|
||||
|
||||
def get_backends():
|
||||
'''Return a dictionnary mapping existing eopayment backends name to their
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import urlparse
|
||||
import string
|
||||
from decimal import Decimal
|
||||
import uuid
|
||||
import hashlib
|
||||
from gettext import gettext as _
|
||||
|
||||
from common import PaymentCommon, FORM, Form, PaymentResponse, PAID, ERROR, CANCELED
|
||||
|
||||
'''
|
||||
Payment backend module for the ATOS/SIPS system used by many French banks.
|
||||
|
||||
It use the middleware given by the bank.
|
||||
|
||||
The necessary options are:
|
||||
|
||||
- pathfile, to indicate the absolute path of the pathfile file given by the
|
||||
bank,
|
||||
- binpath, the path of the directory containing the request and response
|
||||
executables,
|
||||
|
||||
All the other needed parameters SHOULD already be set in the parmcom files
|
||||
contained in the middleware distribution file.
|
||||
|
||||
'''
|
||||
|
||||
__all__ = ['Payment']
|
||||
|
||||
|
||||
class Payment(PaymentCommon):
|
||||
URL = {
|
||||
'test': 'https://payment-webinit.simu.sips-atos.com/paymentInit',
|
||||
'prod': 'https://payment-webinit.sips-atos.com/paymentInit',
|
||||
}
|
||||
INTERFACE_VERSION = 'HP_2.3'
|
||||
RESPONSE_CODES = {
|
||||
'00': 'Authorisation accepted',
|
||||
'02': 'Authorisation request to be performed via telephone with the issuer, as the '
|
||||
'card authorisation threshold has been exceeded, if the forcing is authorised for '
|
||||
'the merchant',
|
||||
'03': 'Invalid distance selling contract',
|
||||
'05': 'Authorisation refused',
|
||||
'12': 'Invalid transaction, verify the parameters transferred in the request.',
|
||||
'14': 'Invalid bank details or card security code',
|
||||
'17': 'Buyer cancellation',
|
||||
'24': 'Operation impossible. The operation the merchant wishes to perform is not '
|
||||
'compatible with the status of the transaction.',
|
||||
'25': 'Transaction not found in the Sips database',
|
||||
'30': 'Format error',
|
||||
'34': 'Suspicion of fraud',
|
||||
'40': 'Function not supported: the operation that the merchant would like to perform '
|
||||
'is not part of the list of operations for which the merchant is authorised',
|
||||
'51': 'Amount too high',
|
||||
'54': 'Card is past expiry date',
|
||||
'60': 'Transaction pending',
|
||||
'63': 'Security rules not observed, transaction stopped',
|
||||
'75': 'Number of attempts at entering the card number exceeded',
|
||||
'90': 'Service temporarily unavailable',
|
||||
'94': 'Duplicated transaction: for a given day, the TransactionReference has already been '
|
||||
'used',
|
||||
'97': 'Timeframe exceeded, transaction refused',
|
||||
'99': 'Temporary problem at the Sips Office Server level',
|
||||
}
|
||||
TEST_MERCHANT_ID = '002001000000001'
|
||||
|
||||
description = {
|
||||
'caption': 'SIPS 2',
|
||||
'parameters': [
|
||||
{
|
||||
'name': 'platform',
|
||||
'caption': _('Platform'),
|
||||
'default': 'test',
|
||||
'choices': ['test', 'prod'],
|
||||
'required': True,
|
||||
},
|
||||
{
|
||||
'name': 'merchand_id',
|
||||
'caption': _('Merchant ID'),
|
||||
'default': TEST_MERCHANT_ID,
|
||||
'required': True,
|
||||
},
|
||||
{
|
||||
'name': 'secret_key',
|
||||
'caption': _('Secret Key'),
|
||||
'default': '002001000000001_KEY1',
|
||||
'required': True,
|
||||
},
|
||||
{
|
||||
'name': 'key_version',
|
||||
'caption': _('Key Version'),
|
||||
'default': '1',
|
||||
'required': True,
|
||||
},
|
||||
{
|
||||
'name': 'normal_return_url',
|
||||
'caption': _('Normal return URL'),
|
||||
'default': 'http://www.example.com/',
|
||||
'required': True,
|
||||
},
|
||||
{
|
||||
'name': 'automatic_return_url',
|
||||
'caption': _('Automatic return URL'),
|
||||
'required': False,
|
||||
},
|
||||
{
|
||||
'name': 'currency_code',
|
||||
'caption': _('Currency code'),
|
||||
'default': '978',
|
||||
'choices': ['978'],
|
||||
'required': True,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
def __init__(self, options, logger=None):
|
||||
super(Payment, self).__init__(options)
|
||||
|
||||
def encode_data(self, data):
|
||||
return u'|'.join(u'%s=%s' % (unicode(key), unicode(value))
|
||||
for key, value in data.iteritems())
|
||||
|
||||
def seal_data(self, data):
|
||||
s = self.encode_data(data)
|
||||
s += unicode(self.secret_key)
|
||||
s = s.encode('utf-8')
|
||||
s = hashlib.sha256(s).hexdigest()
|
||||
return s
|
||||
|
||||
def get_data(self):
|
||||
data = {}
|
||||
data['merchantId'] = self.merchand_id
|
||||
data['keyVersion'] = self.key_version
|
||||
data['normalReturnUrl'] = self.normal_return_url
|
||||
data['currencyCode'] = self.currency_code
|
||||
return data
|
||||
|
||||
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):
|
||||
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['Amount'] = unicode(int(Decimal(amount) * 100))
|
||||
if email:
|
||||
data['customerEmail'] = email
|
||||
if next_url:
|
||||
data['normalReturnUrl'] = next_url
|
||||
form = Form(
|
||||
url=self.get_url(),
|
||||
method='POST',
|
||||
fields=[
|
||||
{
|
||||
'type': 'hidden',
|
||||
'name': 'Data',
|
||||
'value': self.encode_data(data)
|
||||
},
|
||||
{
|
||||
'type': 'hidden',
|
||||
'name': 'Seal',
|
||||
'value': self.seal_data(data),
|
||||
},
|
||||
{
|
||||
'type': 'hidden',
|
||||
'name': 'InterfaceVersion',
|
||||
'value': self.INTERFACE_VERSION,
|
||||
},
|
||||
])
|
||||
self.logger.debug('emitting request %r', data)
|
||||
return transaction_id, FORM, form
|
||||
|
||||
def decode_data(self, data):
|
||||
data = data.split('|')
|
||||
data = [map(unicode, p.split('=')) for p in data]
|
||||
return dict(data)
|
||||
|
||||
def check_seal(self, data, seal):
|
||||
return seal == self.seal_data(data)
|
||||
|
||||
response_code_to_result = {
|
||||
'00': PAID,
|
||||
'17': CANCELED,
|
||||
}
|
||||
|
||||
def response(self, query_string, **kwargs):
|
||||
form = urlparse.parse_qs(query_string)
|
||||
self.logger.debug('received query string %r', form)
|
||||
assert 'Data' in form
|
||||
assert 'Seal' in form
|
||||
assert 'InterfaceVersion' in form
|
||||
data = self.decode_data(form['Data'][0])
|
||||
seal = form['Seal']
|
||||
self.logger.debug('parsed response %r seal %r', data, seal)
|
||||
signed = self.check_seal(data, seal)
|
||||
response_code = data['responseCode']
|
||||
transaction_id = data.get('transactionReference')
|
||||
result = self.response_code_to_result.get(response_code, ERROR)
|
||||
merchant_id = data.get('merchantId')
|
||||
test = merchant_id == self.TEST_MERCHANT_ID
|
||||
return PaymentResponse(
|
||||
result=result,
|
||||
signed=signed,
|
||||
bank_data=data,
|
||||
order_id=transaction_id,
|
||||
transaction_id=data.get('authorisationId'),
|
||||
bank_status=self.RESPONSE_CODES.get(response_code, u'unknown code - ' + response_code),
|
||||
test=test)
|
|
@ -0,0 +1,13 @@
|
|||
import pytest
|
||||
import pprint
|
||||
|
||||
import eopayment
|
||||
|
||||
def test_build_request():
|
||||
backend = eopayment.Payment('sips2', {})
|
||||
assert backend.request(amount='12')
|
||||
|
||||
def test_parse_response():
|
||||
qs = '''Data=captureDay%3D0%7CcaptureMode%3DAUTHOR_CAPTURE%7CcurrencyCode%3D978%7CmerchantId%3D002001000000001%7CorderChannel%3DINTERNET%7CresponseCode%3D00%7CtransactionDateTime%3D2016-02-01T17%3A44%3A20%2B01%3A00%7CtransactionReference%3D668930%7CkeyVersion%3D1%7CacquirerResponseCode%3D00%7Camount%3D1200%7CauthorisationId%3D12345%7CcardCSCResultCode%3D4E%7CpanExpiryDate%3D201605%7CpaymentMeanBrand%3DMASTERCARD%7CpaymentMeanType%3DCARD%7CcustomerIpAddress%3D82.244.203.243%7CmaskedPan%3D5100%23%23%23%23%23%23%23%23%23%23%23%2300%7CorderId%3Dd4903de7027f4d56ac01634fd7ab9526%7CholderAuthentRelegation%3DN%7CholderAuthentStatus%3D3D_ERROR%7CtransactionOrigin%3DINTERNET%7CpaymentPattern%3DONE_SHOT&Seal=6ca3247765a19b45d25ad54ef4076483e7d55583166bd5ac9c64357aac097602&InterfaceVersion=HP_2.0&Encode='''
|
||||
backend = eopayment.Payment('sips2', {})
|
||||
assert backend.response(qs)
|
Loading…
Reference in New Issue