eopayment/eopayment/sips2.py

211 lines
7.3 KiB
Python

# -*- 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['billingContact.email'] = 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)