eopayment/eopayment/__init__.py

165 lines
6.6 KiB
Python

# -*- coding: utf-8 -*-
import importlib
import logging
from .common import (URL, HTML, FORM, RECEIVED, ACCEPTED, PAID, DENIED,
CANCELED, CANCELLED, ERROR, WAITING, ResponseError, force_text)
__all__ = ['Payment', 'URL', 'HTML', 'FORM', 'SIPS',
'SYSTEMPAY', 'SPPLUS', 'TIPI', 'DUMMY', 'get_backend', 'RECEIVED', 'ACCEPTED',
'PAID', 'DENIED', 'CANCELED', 'CANCELLED', 'ERROR', 'WAITING', 'get_backends']
SIPS = 'sips'
SIPS2 = 'sips2'
SYSTEMPAY = 'systempayv2'
SPPLUS = 'spplus'
TIPI = 'tipi'
DUMMY = 'dummy'
OGONE = 'ogone'
PAYBOX = 'paybox'
PAYZEN = 'payzen'
logger = logging.getLogger(__name__)
def get_backend(kind):
'''Resolve a backend name into a module object'''
module = importlib.import_module('.' + kind, package='eopayment')
return module.Payment
__BACKENDS = [ DUMMY, SIPS, SIPS2, SYSTEMPAY, SPPLUS, OGONE, PAYBOX, PAYZEN, TIPI ]
def get_backends():
'''Return a dictionnary mapping existing eopayment backends name to their
description.
>>> get_backends()['dummy'].description['caption']
'Dummy payment backend'
'''
return dict((backend, get_backend(backend)) for backend in __BACKENDS)
class Payment(object):
'''
Interface to credit card online payment servers of French banks. The
only use case supported for now is a unique automatic payment.
>>> spplus_options = { \
'cle': '58 6d fc 9c 34 91 9b 86 3f fd 64 ' \
'63 c9 13 4a 26 ba 29 74 1e c7 e9 80 79', \
'siret': '00000000000001-01', \
}
>>> p = Payment(kind=SPPLUS, options=spplus_options)
>>> transaction_id, kind, data = p.request('10.00', email='bob@example.com')
>>> print (transaction_id, kind, data) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
('...', 1, 'https://www.spplus.net/paiement/init.do?...')
Supported backend of French banks are:
- sips, for BNP, Banque Populaire (before 2010), CCF, HSBC, Crédit
Agricole, La Banque Postale, LCL, Société Générale and Crédit du
Nord.
- spplus for Caisse d'épargne
- systempay for Banque Populaire (after 2010)
For SIPs you also need the bank provided middleware especially the two
executables, request and response, as the protocol from ATOS/SIPS is not
documented. For the other backends the modules are autonomous.
Each backend need some configuration parameters to be used, the
description of the backend list those parameters. The description
dictionary can be used to generate configuration forms.
>>> d = get_backend(SPPLUS).description
>>> print d['caption']
SPPlus payment service of French bank Caisse d'epargne
>>> print [p['name'] for p in d['parameters']] # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
['cle', ..., 'moyen']
>>> print d['parameters'][0]['caption']
Secret key, a 40 digits hexadecimal number
'''
def __init__(self, kind, options, logger=None):
self.kind = kind
self.backend = get_backend(kind)(options, logger=logger)
def request(self, amount, **kwargs):
'''Request a payment to the payment backend.
Arguments:
amount -- the amount of money to ask
email -- the email of the customer (optional)
usually redundant with the hardwired settings in the bank
configuration panel. At this url you must use the Payment.response
method to analyze the bank returned values.
It returns a triple of values, (transaction_id, kind, data):
- the first gives a string value to later match the payment with
the invoice,
- kind gives the type of the third value, payment.URL or
payment.HTML or payment.FORM,
- the third is the URL or the HTML form to contact the payment
server, which must be sent to the customer browser.
'''
logger.debug(u'%r' % kwargs)
for param in kwargs:
# encode all input params to unicode
kwargs[param] = force_text(kwargs[param])
return self.backend.request(amount, **kwargs)
def response(self, query_string, **kwargs):
'''
Process a response from the Bank API. It must be used on the URL
where the user browser of the payment server is going to post the
result of the payment. Beware it can happen multiple times for the
same payment, so you MUST support multiple notification of the same
event, i.e. it should be idempotent. For example if you already
validated some invoice, receiving a new payment notification for the
same invoice should alter this state change.
Beware that when notified directly by the bank (and not through the
customer browser) no applicative session will exist, so you should
not depend on it in your handler.
Arguments:
query_string -- the URL encoded form-data from a GET or a POST
It returns a quadruplet of values:
(result, transaction_id, bank_data, return_content)
- result is a boolean stating whether the transaction worked, use it
to decide whether to act on a valid payment,
- the transaction_id return the same id than returned by request
when requesting for the payment, use it to find the invoice or
transaction which is linked to the payment,
- bank_data is a dictionnary of the data sent by the bank, it should
be logged for security reasons,
- return_content, if not None you must return this content as the
result of the HTTP request, it's used when the bank is calling
your site as a web service.
'''
return self.backend.response(query_string, **kwargs)
def cancel(self, amount, bank_data, **kwargs):
'''
Cancel or edit the amount of a transaction sent to the bank.
Arguments:
- amount -- the amount of money to cancel
- bank_data -- the transaction dictionary received from the bank
'''
return self.backend.cancel(amount, bank_data, **kwargs)
def validate(self, amount, bank_data, **kwargs):
'''
Validate and trigger the transmission of a transaction to the bank.
Arguments:
- amount -- the amount of money
- bank_data -- the transaction dictionary received from the bank
'''
return self.backend.validate(amount, bank_data, **kwargs)