2011-04-22 17:21:16 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2016-07-05 13:22:11 +02:00
|
|
|
import logging
|
|
|
|
|
2016-03-09 22:31:40 +01:00
|
|
|
from common import (URL, HTML, FORM, RECEIVED, ACCEPTED, PAID, DENIED,
|
2017-10-11 10:09:44 +02:00
|
|
|
CANCELED, CANCELLED, ERROR, WAITING, ResponseError, force_text)
|
2011-04-22 17:21:16 +02:00
|
|
|
|
2015-11-30 13:21:48 +01:00
|
|
|
__all__ = ['Payment', 'URL', 'HTML', 'FORM', 'SIPS',
|
2015-03-05 17:08:31 +01:00
|
|
|
'SYSTEMPAY', 'SPPLUS', 'TIPI', 'DUMMY', 'get_backend', 'RECEIVED', 'ACCEPTED',
|
2017-10-11 10:09:44 +02:00
|
|
|
'PAID', 'DENIED', 'CANCELED', 'CANCELLED', 'ERROR', 'WAITING', 'get_backends']
|
2011-04-29 18:02:18 +02:00
|
|
|
|
2011-04-22 17:21:16 +02:00
|
|
|
SIPS = 'sips'
|
2016-02-01 17:54:58 +01:00
|
|
|
SIPS2 = 'sips2'
|
2011-04-22 17:21:16 +02:00
|
|
|
SYSTEMPAY = 'systempayv2'
|
|
|
|
SPPLUS = 'spplus'
|
2014-03-26 16:19:02 +01:00
|
|
|
TIPI = 'tipi'
|
2011-04-30 19:04:50 +02:00
|
|
|
DUMMY = 'dummy'
|
2015-03-04 17:19:50 +01:00
|
|
|
OGONE = 'ogone'
|
2015-07-16 11:50:18 +02:00
|
|
|
PAYBOX = 'paybox'
|
2015-11-03 23:16:40 +01:00
|
|
|
PAYZEN = 'payzen'
|
2011-04-22 17:21:16 +02:00
|
|
|
|
2016-07-05 13:22:11 +02:00
|
|
|
logger = logging.getLogger(__name__)
|
2012-06-13 13:45:46 +02:00
|
|
|
|
2011-04-30 22:12:39 +02:00
|
|
|
def get_backend(kind):
|
|
|
|
'''Resolve a backend name into a module object'''
|
2011-05-02 11:13:22 +02:00
|
|
|
module = __import__(kind, globals(), locals(), [])
|
2011-04-30 22:12:39 +02:00
|
|
|
return module.Payment
|
|
|
|
|
2016-05-12 10:38:45 +02:00
|
|
|
__BACKENDS = [ DUMMY, SIPS, SIPS2, SYSTEMPAY, SPPLUS, OGONE, PAYBOX, PAYZEN, TIPI ]
|
2015-03-04 14:48:38 +01:00
|
|
|
|
|
|
|
def get_backends():
|
|
|
|
'''Return a dictionnary mapping existing eopayment backends name to their
|
|
|
|
description.
|
|
|
|
|
|
|
|
>>> get_backends()['dummy'].description['caption']
|
|
|
|
'Dummy payment backend'
|
|
|
|
|
|
|
|
'''
|
2015-03-09 09:53:47 +01:00
|
|
|
return dict((backend, get_backend(backend)) for backend in __BACKENDS)
|
2012-06-13 13:45:46 +02:00
|
|
|
|
2011-04-22 17:21:16 +02:00
|
|
|
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.
|
|
|
|
|
2015-03-04 14:49:16 +01:00
|
|
|
>>> 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', \
|
2011-04-22 17:21:16 +02:00
|
|
|
}
|
|
|
|
>>> p = Payment(kind=SPPLUS, options=spplus_options)
|
2016-02-16 10:51:41 +01:00
|
|
|
>>> transaction_id, kind, data = p.request('10.00', email='bob@example.com')
|
2015-03-04 14:49:16 +01:00
|
|
|
>>> print (transaction_id, kind, data) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
|
|
|
('...', 1, 'https://www.spplus.net/paiement/init.do?...')
|
2011-04-22 17:21:16 +02:00
|
|
|
|
|
|
|
Supported backend of French banks are:
|
2011-04-30 19:04:50 +02:00
|
|
|
|
2011-04-22 17:21:16 +02:00
|
|
|
- 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.
|
|
|
|
|
2011-04-30 19:04:50 +02:00
|
|
|
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.
|
|
|
|
|
2015-03-04 14:49:16 +01:00
|
|
|
>>> d = get_backend(SPPLUS).description
|
2011-04-30 19:04:50 +02:00
|
|
|
>>> print d['caption']
|
2015-03-04 14:49:16 +01:00
|
|
|
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
|
2011-04-30 19:04:50 +02:00
|
|
|
|
2011-04-22 17:21:16 +02:00
|
|
|
'''
|
|
|
|
|
2016-02-18 16:05:58 +01:00
|
|
|
def __init__(self, kind, options, logger=None):
|
2011-04-22 17:21:16 +02:00
|
|
|
self.kind = kind
|
2016-02-18 16:05:58 +01:00
|
|
|
self.backend = get_backend(kind)(options, logger=logger)
|
2011-04-22 17:21:16 +02:00
|
|
|
|
2012-06-03 10:30:48 +02:00
|
|
|
def request(self, amount, **kwargs):
|
2011-04-22 17:21:16 +02:00
|
|
|
'''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
|
2015-07-16 14:12:53 +02:00
|
|
|
payment.HTML or payment.FORM,
|
2011-04-22 17:21:16 +02:00
|
|
|
- the third is the URL or the HTML form to contact the payment
|
|
|
|
server, which must be sent to the customer browser.
|
|
|
|
'''
|
2016-07-05 13:22:11 +02:00
|
|
|
logger.debug(u'%r' % kwargs)
|
2016-10-19 12:06:03 +02:00
|
|
|
for param in kwargs:
|
|
|
|
# encode all input params to unicode
|
|
|
|
kwargs[param] = force_text(kwargs[param])
|
2012-06-03 10:30:48 +02:00
|
|
|
return self.backend.request(amount, **kwargs)
|
2011-04-22 17:21:16 +02:00
|
|
|
|
2015-07-16 14:12:15 +02:00
|
|
|
def response(self, query_string, **kwargs):
|
2011-04-22 17:21:16 +02:00
|
|
|
'''
|
|
|
|
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:
|
2012-06-13 13:45:46 +02:00
|
|
|
|
2011-04-22 17:21:16 +02:00
|
|
|
(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.
|
|
|
|
|
|
|
|
'''
|
2015-07-16 14:12:15 +02:00
|
|
|
return self.backend.response(query_string, **kwargs)
|
2016-07-27 13:10:57 +02:00
|
|
|
|
|
|
|
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)
|