add sips2 backend (#9824)

It implements the interface version 2.3 of SIPS.
This commit is contained in:
Benjamin Dauvergne 2016-02-01 17:54:58 +01:00
parent 6990c40857
commit 1d659b1b21
4 changed files with 237 additions and 7 deletions

View File

@ -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

View File

@ -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

210
eopayment/sips2.py Normal file
View File

@ -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)

13
tests/sips2.py Normal file
View File

@ -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)