diff --git a/eopayment/__init__.py b/eopayment/__init__.py index 17c9038..11dc254 100644 --- a/eopayment/__init__.py +++ b/eopayment/__init__.py @@ -1,18 +1,35 @@ # -*- coding: utf-8 -*- +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . import datetime import importlib import logging import pytz +from .common import ( # noqa: F401 + URL, HTML, FORM, RECEIVED, ACCEPTED, PAID, DENIED, + CANCELED, CANCELLED, ERROR, WAITING, force_text, + ResponseError, +) -from .common import (URL, HTML, FORM, RECEIVED, ACCEPTED, PAID, DENIED, - CANCELED, CANCELLED, ERROR, WAITING, ResponseError, force_text, - PaymentException) - -__all__ = ['Payment', 'URL', 'HTML', 'FORM', 'SIPS', -'SYSTEMPAY', 'SPPLUS', 'TIPI', 'DUMMY', 'get_backend', 'RECEIVED', 'ACCEPTED', -'PAID', 'DENIED', 'CANCELED', 'CANCELLED', 'ERROR', 'WAITING', 'get_backends', 'PAYFIP_WS'] +__all__ = ['Payment', 'URL', 'HTML', 'FORM', 'SIPS', 'SYSTEMPAY', 'SPPLUS', + 'TIPI', 'DUMMY', 'get_backend', 'RECEIVED', 'ACCEPTED', 'PAID', + 'DENIED', 'CANCELED', 'CANCELLED', 'ERROR', 'WAITING', + 'get_backends', 'PAYFIP_WS'] SIPS = 'sips' SIPS2 = 'sips2' @@ -27,12 +44,15 @@ PAYFIP_WS = 'payfip_ws' 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, PAYFIP_WS ] +__BACKENDS = [DUMMY, SIPS, SIPS2, SYSTEMPAY, SPPLUS, OGONE, PAYBOX, PAYZEN, + TIPI, PAYFIP_WS] + def get_backends(): '''Return a dictionnary mapping existing eopayment backends name to their @@ -44,6 +64,7 @@ def get_backends(): ''' return dict((backend, get_backend(backend)) for backend in __BACKENDS) + class Payment(object): ''' Interface to credit card online payment servers of French banks. The diff --git a/eopayment/cb.py b/eopayment/cb.py index 7b0443f..ccd7f69 100644 --- a/eopayment/cb.py +++ b/eopayment/cb.py @@ -1,4 +1,19 @@ # -*- coding: utf-8 -*- +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . '''Responses codes emitted by EMV Card or 'Carte Bleu' in France''' diff --git a/eopayment/common.py b/eopayment/common.py index 66578e7..b79d9d4 100644 --- a/eopayment/common.py +++ b/eopayment/common.py @@ -1,3 +1,19 @@ +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + import os.path import os import random @@ -54,6 +70,7 @@ def force_text(s, encoding='utf-8'): return six.text_type(s, encoding, 'ignore') return s + def force_byte(s, encoding='utf-8'): if isinstance(s, bytes): return s @@ -144,7 +161,7 @@ class PaymentCommon(object): try: fd = os.open(os.path.join(self.PATH, name), os.O_CREAT | os.O_EXCL) - except: + except Exception: raise else: os.close(fd) diff --git a/eopayment/dummy.py b/eopayment/dummy.py index 98c63e0..6a4ede9 100644 --- a/eopayment/dummy.py +++ b/eopayment/dummy.py @@ -1,20 +1,46 @@ +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + import string import logging import warnings -def N_(message): return message - from six.moves.urllib.parse import parse_qs, urlencode -from .common import (PaymentCommon, URL, PaymentResponse, PAID, ERROR, WAITING, - ResponseError, force_text) +from .common import ( + PaymentCommon, + PaymentResponse, + ResponseError, + URL, + PAID, ERROR, WAITING, + force_text +) + +__all__ = ['Payment'] + + +def N_(message): + return message -__all__ = [ 'Payment' ] SERVICE_URL = 'http://dummy-payment.demo.entrouvert.com/' ALPHANUM = string.ascii_letters + string.digits LOGGER = logging.getLogger(__name__) + class Payment(PaymentCommon): ''' Dummy implementation of the payment interface. @@ -36,59 +62,68 @@ class Payment(PaymentCommon): per request basis). ''' description = { - 'caption': 'Dummy payment backend', - 'parameters': [ - { - 'name': 'normal_return_url', - 'caption': N_('Normal return URL'), - 'default': '', - 'required': True, - }, - { - 'name': 'automatic_return_url', - 'caption': N_('Automatic return URL'), - 'required': False, - }, - { 'name': 'dummy_service_url', - 'caption': 'URL of the dummy payment service', - 'default': SERVICE_URL, - 'type': str, - }, - { 'name': 'origin', - 'caption': 'name of the requesting service, ' - 'to present in the user interface', - 'type': str, - - }, - { 'name': 'siret', - 'caption': 'dummy siret parameter', - 'type': str, - }, - { 'name': 'consider_all_response_signed', - 'caption': 'All response will be considered as signed ' - '(to test payment locally for example, as you ' - 'cannot received the signed callback)', - 'type': bool, - 'default': False, - }, - { 'name': 'direct_notification_url', - 'caption': 'direct notification url (replaced by automatic_return_url)', - 'type': str, - 'deprecated': True, - }, - { 'name': 'next_url (replaced by normal_return_url)', - 'caption': 'Return URL for the user', - 'type': str, - 'deprecated': True, - }, - ], + 'caption': 'Dummy payment backend', + 'parameters': [ + { + 'name': 'normal_return_url', + 'caption': N_('Normal return URL'), + 'default': '', + 'required': True, + }, + { + 'name': 'automatic_return_url', + 'caption': N_('Automatic return URL'), + 'required': False, + }, + { + 'name': 'dummy_service_url', + 'caption': 'URL of the dummy payment service', + 'default': SERVICE_URL, + 'type': str, + }, + { + 'name': 'origin', + 'caption': 'name of the requesting service, ' + 'to present in the user interface', + 'type': str, + }, + { + 'name': 'siret', + 'caption': 'dummy siret parameter', + 'type': str, + }, + { + 'name': 'consider_all_response_signed', + 'caption': ( + 'All response will be considered as signed ' + '(to test payment locally for example, as you ' + 'cannot received the signed callback)' + ), + 'type': bool, + 'default': False, + }, + { + 'name': 'direct_notification_url', + 'caption': 'direct notification url (replaced by automatic_return_url)', + 'type': str, + 'deprecated': True, + }, + { + 'name': 'next_url (replaced by normal_return_url)', + 'caption': 'Return URL for the user', + 'type': str, + 'deprecated': True, + }, + ], } def request(self, amount, name=None, address=None, email=None, phone=None, - orderid=None, info1=None, info2=None, info3=None, next_url=None, **kwargs): - self.logger.debug('%s amount %s name %s address %s email %s phone %s' - ' next_url %s info1 %s info2 %s info3 %s kwargs: %s', - __name__, amount, name, address, email, phone, info1, info2, info3, next_url, kwargs) + orderid=None, info1=None, info2=None, info3=None, + next_url=None, **kwargs): + self.logger.debug( + '%s amount %s name %s address %s email %s phone %s' + ' next_url %s info1 %s info2 %s info3 %s kwargs: %s', + __name__, amount, name, address, email, phone, info1, info2, info3, next_url, kwargs) transaction_id = self.transaction_id(30, ALPHANUM, 'dummy', self.siret) normal_return_url = self.normal_return_url if next_url and not normal_return_url: @@ -101,16 +136,17 @@ class Payment(PaymentCommon): "use automatic_return_url", DeprecationWarning) automatic_return_url = self.direct_notification_url query = { - 'transaction_id': transaction_id, - 'siret': self.siret, - 'amount': amount, - 'email': email, - 'return_url': normal_return_url or '', - 'direct_notification_url': automatic_return_url or '', - 'origin': self.origin + 'transaction_id': transaction_id, + 'siret': self.siret, + 'amount': amount, + 'email': email, + 'return_url': normal_return_url or '', + 'direct_notification_url': automatic_return_url or '', + 'origin': self.origin } - query.update(dict(name=name, address=address, email=email, phone=phone, - orderid=orderid, info1=info1, info2=info2, info3=info3)) + query.update( + dict(name=name, address=address, email=email, phone=phone, + orderid=orderid, info1=info1, info2=info2, info3=info3)) for key in list(query.keys()): if query[key] is None: del query[key] @@ -119,9 +155,9 @@ class Payment(PaymentCommon): def response(self, query_string, logger=LOGGER, **kwargs): form = parse_qs(force_text(query_string)) - if not 'transaction_id' in form: + if 'transaction_id' not in form: raise ResponseError('missing transaction_id') - transaction_id = form.get('transaction_id',[''])[0] + transaction_id = form.get('transaction_id', [''])[0] form[self.BANK_ID] = transaction_id signed = 'signed' in form @@ -134,14 +170,15 @@ class Payment(PaymentCommon): if 'waiting' in form: result = WAITING - response = PaymentResponse(result=result, - signed=signed, - bank_data=form, - return_content=content, - order_id=transaction_id, - transaction_id=transaction_id, - bank_status=form.get('reason'), - test=True) + response = PaymentResponse( + result=result, + signed=signed, + bank_data=form, + return_content=content, + order_id=transaction_id, + transaction_id=transaction_id, + bank_status=form.get('reason'), + test=True) return response def validate(self, amount, bank_data, **kwargs): diff --git a/eopayment/ogone.py b/eopayment/ogone.py index f2788ec..dc304b3 100644 --- a/eopayment/ogone.py +++ b/eopayment/ogone.py @@ -1,14 +1,35 @@ # -*- coding: utf-8 -*- +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + import hashlib import string import six from six.moves.urllib import parse as urlparse from decimal import Decimal, ROUND_HALF_UP -from .common import (PaymentCommon, PaymentResponse, FORM, CANCELLED, PAID, - ERROR, Form, DENIED, ACCEPTED, ORDERID_TRANSACTION_SEPARATOR, - WAITING, ResponseError, force_byte, force_text) -def N_(message): return message +from .common import ( + PaymentCommon, PaymentResponse, FORM, CANCELLED, PAID, + ERROR, Form, DENIED, ACCEPTED, ORDERID_TRANSACTION_SEPARATOR, + WAITING, ResponseError, force_byte, force_text +) + + +def N_(message): + return message ENVIRONMENT_TEST = 'TEST' ENVIRONMENT_TEST_URL = 'https://secure.ogone.com/ncol/test/orderstandard.asp' @@ -409,6 +430,7 @@ TRXDATE VC """.split() + class Payment(PaymentCommon): # See http://payment-services.ingenico.com/fr/fr/ogone/support/guides/integration%20guides/e-commerce description = { @@ -425,33 +447,40 @@ class Payment(PaymentCommon): 'caption': N_('Automatic return URL (ignored, must be set in Ogone backoffice)'), 'required': False, }, - {'name': 'environment', + { + 'name': 'environment', 'default': ENVIRONMENT_TEST, 'caption': N_(u'Environnement'), 'choices': ENVIRONMENT, }, - {'name': 'pspid', + { + 'name': 'pspid', 'caption': N_(u"Nom d'affiliation dans le système"), 'required': True, }, - {'name': 'language', + { + 'name': 'language', 'caption': N_(u'Langage'), 'default': 'fr_FR', 'choices': (('fr_FR', N_('français')),), }, - {'name': 'hash_algorithm', + { + 'name': 'hash_algorithm', 'caption': N_(u'Algorithme de hachage'), 'default': 'sha1', }, - {'name': 'sha_in', + { + 'name': 'sha_in', 'caption': N_(u'Clé SHA-IN'), 'required': True, }, - {'name': 'sha_out', + { + 'name': 'sha_out', 'caption': N_(u'Clé SHA-OUT'), 'required': True, }, - {'name': 'currency', + { + 'name': 'currency', 'caption': N_(u'Monnaie'), 'default': 'EUR', 'choices': ('EUR',), @@ -485,7 +514,7 @@ class Payment(PaymentCommon): raise NotImplementedError('unknown environment %s' % self.environment) def request(self, amount, orderid=None, name=None, email=None, - language=None, description=None, **kwargs): + language=None, description=None, **kwargs): reference = self.transaction_id(20, string.digits + string.ascii_letters) @@ -493,18 +522,22 @@ class Payment(PaymentCommon): if orderid: if len(orderid) > 24: raise ValueError('orderid length exceeds 25 characters') - reference = orderid + ORDERID_TRANSACTION_SEPARATOR + self.transaction_id(29-len(orderid), string.digits + string.ascii_letters) + reference = ( + orderid + + ORDERID_TRANSACTION_SEPARATOR + + self.transaction_id(29 - len(orderid), + string.digits + string.ascii_letters)) language = language or self.language # convertir en centimes amount = Decimal(amount) * 100 # arrondi comptable francais amount = amount.quantize(Decimal('1.'), rounding=ROUND_HALF_UP) params = { - 'AMOUNT': force_text(amount), - 'ORDERID': reference, - 'PSPID': self.pspid, - 'LANGUAGE': language, - 'CURRENCY': self.currency, + 'AMOUNT': force_text(amount), + 'ORDERID': reference, + 'PSPID': self.pspid, + 'LANGUAGE': language, + 'CURRENCY': self.currency, } if self.normal_return_url: params['ACCEPTURL'] = self.normal_return_url @@ -526,11 +559,11 @@ class Payment(PaymentCommon): params[key] = force_text(params[key]) url = self.get_request_url() form = Form( - url=url, - method='POST', - fields=[{'type': 'hidden', - 'name': key, - 'value': params[key]} for key in params]) + url=url, + method='POST', + fields=[{'type': 'hidden', + 'name': key, + 'value': params[key]} for key in params]) return reference, FORM, form def response(self, query_string, **kwargs): @@ -570,14 +603,14 @@ class Payment(PaymentCommon): result = WAITING else: self.logger.error('response STATUS=%s NCERROR=%s NCERRORPLUS=%s', - status, error, params.get('NCERRORPLUS', '')) + status, error, params.get('NCERRORPLUS', '')) result = ERROR # extract reference from received order id if ORDERID_TRANSACTION_SEPARATOR in reference: reference, transaction_id = reference.split(ORDERID_TRANSACTION_SEPARATOR, 1) return PaymentResponse( - result=result, - signed=signed, - bank_data=params, - order_id=reference, - transaction_id=transaction_id) + result=result, + signed=signed, + bank_data=params, + order_id=reference, + transaction_id=transaction_id) diff --git a/eopayment/paybox.py b/eopayment/paybox.py index 130925e..c629f68 100644 --- a/eopayment/paybox.py +++ b/eopayment/paybox.py @@ -1,4 +1,19 @@ # -*- coding: utf-8 +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . import codecs from collections import OrderedDict @@ -289,7 +304,9 @@ class Payment(PaymentCommon): 'signature:K' ) d['PBX_HASH'] = 'SHA512' - d['PBX_TIME'] = kwargs.get('time') or (force_text(datetime.datetime.utcnow().isoformat('T')).split('.')[0]+'+00:00') + d['PBX_TIME'] = kwargs.get('time') or ( + force_text(datetime.datetime.utcnow().isoformat('T')).split('.')[0] + + '+00:00') d['PBX_ARCHIVAGE'] = transaction_id if self.normal_return_url: d['PBX_EFFECTUE'] = self.normal_return_url diff --git a/eopayment/payfip_ws.py b/eopayment/payfip_ws.py index 2b6e2c1..4446f8a 100644 --- a/eopayment/payfip_ws.py +++ b/eopayment/payfip_ws.py @@ -1,5 +1,5 @@ # eopayment - online payment library -# Copyright (C) 2011-2019 Entr'ouvert +# Copyright (C) 2011-2020 Entr'ouvert # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Affero General Public License as published diff --git a/eopayment/payzen.py b/eopayment/payzen.py index 9d9cdb9..3059bb4 100644 --- a/eopayment/payzen.py +++ b/eopayment/payzen.py @@ -1,9 +1,26 @@ +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + from copy import deepcopy from . import systempayv2 __all__ = ['Payment'] + class Payment(systempayv2.Payment): service_url = 'https://secure.payzen.eu/vads-payment/' diff --git a/eopayment/sips.py b/eopayment/sips.py index 9c06d83..669264a 100644 --- a/eopayment/sips.py +++ b/eopayment/sips.py @@ -1,4 +1,20 @@ # -*- coding: utf-8 -*- +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + from six.moves.urllib import parse as urlparse import string import subprocess @@ -34,23 +50,25 @@ __all__ = ['Payment'] BINPATH = 'binpath' PATHFILE = 'pathfile' AUTHORISATION_ID = 'authorisation_id' -REQUEST_VALID_PARAMS = ['merchant_id', 'merchant_country', 'amount', - 'currency_code', 'pathfile', 'normal_return_url', 'cancel_return_url', - 'automatic_response_url', 'language', 'payment_means', 'header_flag', - 'capture_day', 'capture_mode', 'bgcolor', 'block_align', 'block_order', - 'textcolor', 'receipt_complement', 'caddie', 'customer_id', - 'customer_email', 'customer_ip_address', 'data', 'return_context', - 'target', 'order_id'] +REQUEST_VALID_PARAMS = [ + 'merchant_id', 'merchant_country', 'amount', 'currency_code', 'pathfile', + 'normal_return_url', 'cancel_return_url', 'automatic_response_url', + 'language', 'payment_means', 'header_flag', 'capture_day', 'capture_mode', + 'bgcolor', 'block_align', 'block_order', 'textcolor', 'receipt_complement', + 'caddie', 'customer_id', 'customer_email', 'customer_ip_address', 'data', + 'return_context', 'target', 'order_id', +] -RESPONSE_PARAMS = ['code', 'error', 'merchant_id', 'merchant_country', - 'amount', 'transaction_id', 'payment_means', 'transmission_date', - 'payment_time', 'payment_date', 'response_code', 'payment_certificate', - AUTHORISATION_ID, 'currency_code', 'card_number', 'cvv_flag', - 'cvv_response_code', 'bank_response_code', 'complementary_code', - 'complementary_info', 'return_context', 'caddie', 'receipt_complement', - 'merchant_language', 'language', 'customer_id', 'order_id', - 'customer_email', 'customer_ip_address', 'capture_day', 'capture_mode', - 'data', ] +RESPONSE_PARAMS = [ + 'code', 'error', 'merchant_id', 'merchant_country', 'amount', + 'transaction_id', 'payment_means', 'transmission_date', 'payment_time', + 'payment_date', 'response_code', 'payment_certificate', AUTHORISATION_ID, + 'currency_code', 'card_number', 'cvv_flag', 'cvv_response_code', + 'bank_response_code', 'complementary_code', 'complementary_info', + 'return_context', 'caddie', 'receipt_complement', 'merchant_language', + 'language', 'customer_id', 'order_id', 'customer_email', + 'customer_ip_address', 'capture_day', 'capture_mode', 'data', +] DATA = 'DATA' PARAMS = 'params' @@ -69,37 +87,38 @@ LOGGER = logging.getLogger(__name__) CB_BANK_RESPONSE_CODES = CB_RESPONSE_CODES AMEX_BANK_RESPONSE_CODE = { -'00': 'Transaction approuvée ou traitée avec succès', -'02': 'Dépassement de plafond', -'04': 'Conserver la carte', -'05': 'Ne pas honorer', -'97': 'Échéance de la temporisation de surveillance globale', + '00': 'Transaction approuvée ou traitée avec succès', + '02': 'Dépassement de plafond', + '04': 'Conserver la carte', + '05': 'Ne pas honorer', + '97': 'Échéance de la temporisation de surveillance globale', } FINAREF_BANK_RESPONSE_CODE = { -'00': 'Transaction approuvée', -'03': 'Commerçant inconnu - Identifiant de commerçant incorrect', -'05': 'Compte / Porteur avec statut bloqué ou invalide', -'11': 'Compte / porteur inconnu', -'16': 'Provision insuffisante', -'20': 'Commerçant invalide - Code monnaie incorrect - ' + \ - 'Opération commerciale inconnue - Opération commerciale invalide', -'80': 'Transaction approuvée avec dépassement', -'81': 'Transaction approuvée avec augmentation capital', -'82': 'Transaction approuvée NPAI', -'83': 'Compte / porteur invalide', + '00': 'Transaction approuvée', + '03': 'Commerçant inconnu - Identifiant de commerçant incorrect', + '05': 'Compte / Porteur avec statut bloqué ou invalide', + '11': 'Compte / porteur inconnu', + '16': 'Provision insuffisante', + '20': ( + 'Commerçant invalide - Code monnaie incorrect - ' + 'Opération commerciale inconnue - Opération commerciale invalide' + ), + '80': 'Transaction approuvée avec dépassement', + '81': 'Transaction approuvée avec augmentation capital', + '82': 'Transaction approuvée NPAI', + '83': 'Compte / porteur invalide', } class Payment(PaymentCommon): description = { - 'caption': 'SIPS', - 'parameters': [{ - 'name': 'merchand_id', - }, - {'name': 'merchant_country', }, - {'name': 'currency_code', } - ], + 'caption': 'SIPS', + 'parameters': [ + {'name': 'merchand_id'}, + {'name': 'merchant_country', }, + {'name': 'currency_code', } + ], } def __init__(self, options, logger=None): @@ -114,8 +133,11 @@ class Payment(PaymentCommon): executable = os.path.join(self.binpath, executable) args = [executable] + ["%s=%s" % p for p in params.items()] self.logger.debug('executing %s' % args) - result,_ = subprocess.Popen(' '.join(args), - stdout=subprocess.PIPE, shell=True).communicate() + result, _ = subprocess.Popen( + ' '.join(args), + stdout=subprocess.PIPE, + shell=True + ).communicate() try: if result[0] == '!': result = result[1:] @@ -133,11 +155,11 @@ class Payment(PaymentCommon): params.update(self.options) return params - def request(self, amount, name=None, address=None, email=None, phone=None, orderid=None, - info1=None, info2=None, info3=None, next_url=None, **kwargs): + def request(self, amount, name=None, address=None, email=None, phone=None, + orderid=None, info1=None, info2=None, info3=None, + next_url=None, **kwargs): params = self.get_request_params() - transaction_id = self.transaction_id(6, string.digits, 'sips', - params[MERCHANT_ID]) + transaction_id = self.transaction_id(6, string.digits, 'sips', params[MERCHANT_ID]) params[TRANSACTION_ID] = transaction_id params[ORDER_ID] = orderid or str(uuid.uuid4()) params[ORDER_ID] = params[ORDER_ID].replace('-', '') @@ -159,7 +181,7 @@ class Payment(PaymentCommon): def response(self, query_string, **kwargs): form = urlparse.parse_qs(query_string) - if not DATA in form: + if DATA not in form: raise ResponseError('missing %s' % DATA) params = {'message': form[DATA][0]} result = self.execute('response', params) @@ -170,10 +192,10 @@ class Payment(PaymentCommon): response_result = d.get(RESPONSE_CODE) == '00' response_code_msg = CB_BANK_RESPONSE_CODES.get(d.get(RESPONSE_CODE)) response = PaymentResponse( - result=response_result, - signed=response_result, - bank_data=d, - order_id=d.get(ORDER_ID), - transaction_id=d.get(AUTHORISATION_ID), - bank_status=response_code_msg) + result=response_result, + signed=response_result, + bank_data=d, + order_id=d.get(ORDER_ID), + transaction_id=d.get(AUTHORISATION_ID), + bank_status=response_code_msg) return response diff --git a/eopayment/sips2.py b/eopayment/sips2.py index 80dfd2b..f84f5a6 100644 --- a/eopayment/sips2.py +++ b/eopayment/sips2.py @@ -1,4 +1,19 @@ # -*- coding: utf-8 -*- +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . import datetime from decimal import Decimal diff --git a/eopayment/spplus.py b/eopayment/spplus.py index 771a672..1b8e6db 100644 --- a/eopayment/spplus.py +++ b/eopayment/spplus.py @@ -1,7 +1,22 @@ # -*- coding: utf-8 -*- +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + from decimal import Decimal import binascii -from gettext import gettext as _ import hmac import hashlib from six.moves.urllib import parse as urlparse @@ -13,10 +28,14 @@ import re import warnings import Crypto.Cipher.DES -from .common import (PaymentCommon, URL, PaymentResponse, RECEIVED, ACCEPTED, - PAID, ERROR, ResponseError, force_byte) +from .common import ( + PaymentCommon, URL, PaymentResponse, RECEIVED, ACCEPTED, + PAID, ERROR, ResponseError, force_byte +) -def N_(message): return message + +def N_(message): + return message __all__ = ['Payment'] @@ -39,7 +58,7 @@ SPPLUS_RESPONSE_CODES = { '8': 'Chèque encaissé', '10': 'Paiement terminé', '11': 'Echéance du paiement annulée par le commerçant', - '12': 'Abandon de l\’internaute', + '12': 'Abandon de l’internaute', '15': 'Remboursement enregistré', '16': 'Remboursement annulé', '17': 'Remboursement accepté', @@ -59,10 +78,12 @@ def decrypt_ntkey(ntkey): key = binascii.unhexlify(ntkey.replace(b' ', b'')) return decrypt_key(key) + def decrypt_key(key): CIPHER = Crypto.Cipher.DES.new(KEY_DES_KEY, Crypto.Cipher.DES.MODE_CBC, IV) return CIPHER.decrypt(key) + def extract_values(query_string): kvs = query_string.split('&') result = [] @@ -72,88 +93,104 @@ def extract_values(query_string): result.append(v) return force_byte(''.join(result)) + def sign_ntkey_query(ntkey, query): key = decrypt_ntkey(ntkey) data_to_sign = extract_values(query) return hmac.new(key[:20], data_to_sign, hashlib.sha1).hexdigest().upper() -PAIEMENT_FIELDS = [ 'siret', REFERENCE, 'langue', 'devise', 'montant', - 'taxe', 'validite' ] +PAIEMENT_FIELDS = [ + 'siret', REFERENCE, 'langue', 'devise', 'montant', 'taxe', 'validite' +] + def sign_url_paiement(ntkey, query): if '?' in query: - query = query[query.index('?')+1:] + query = query[query.index('?') + 1:] key = decrypt_ntkey(ntkey) data = urlparse.parse_qs(query, True) - fields = [data.get(field,[''])[0] for field in PAIEMENT_FIELDS] + fields = [data.get(field, [''])[0] for field in PAIEMENT_FIELDS] data_to_sign = ''.join(fields) return hmac.new(key[:20], data_to_sign, hashlib.sha1).hexdigest().upper() ALPHANUM = string.ascii_letters + string.digits SERVICE_URL = "https://www.spplus.net/paiement/init.do" + class Payment(PaymentCommon): description = { - 'caption': "SPPlus payment service of French bank Caisse d'epargne", - 'parameters': [ - { - 'name': 'normal_return_url', - 'caption': N_('Normal return URL'), - 'default': '', - 'required': True, - }, - { - 'name': 'automatic_return_url', - 'caption': N_('Automatic return URL'), - 'required': False, - }, - { 'name': 'cle', - 'caption': 'Secret key, a 40 digits hexadecimal number', - 'regexp': re.compile('^ *((?:[a-fA-F0-9] *){40}) *$') - }, - { 'name': 'siret', - 'caption': 'Siret of the entreprise augmented with the ' - 'site number, example: 00000000000001-01', - 'regexp': re.compile('^ *(\d{14}-\d{2}) *$') - }, - { 'name': 'langue', - 'caption': 'Language of the customers', - 'default': 'FR', - }, - { 'name': 'taxe', - 'caption': 'Taxes', - 'default': '0.00' - }, - { 'name': 'modalite', - 'caption': '1x, 2x, 3x, xx, nx (if multiple separated by "/")', - 'default': '1x', - }, - { 'name': 'moyen', - 'caption': 'AUR, AMX, CBS, CGA, ' - 'CHK, DIN, PRE (if multiple separate by "/")', - 'default': 'CBS', - }, - ] + 'caption': "SPPlus payment service of French bank Caisse d'epargne", + 'parameters': [ + { + 'name': 'normal_return_url', + 'caption': N_('Normal return URL'), + 'default': '', + 'required': True, + }, + { + 'name': 'automatic_return_url', + 'caption': N_('Automatic return URL'), + 'required': False, + }, + { + 'name': 'cle', + 'caption': 'Secret key, a 40 digits hexadecimal number', + 'regexp': re.compile('^ *((?:[a-fA-F0-9] *){40}) *$') + }, + { + 'name': 'siret', + 'caption': ( + 'Siret of the entreprise augmented with the ' + 'site number, example: 00000000000001-01' + ), + 'regexp': re.compile(r'^ *(\d{14}-\d{2}) *$') + }, + { + 'name': 'langue', + 'caption': 'Language of the customers', + 'default': 'FR', + }, + { + 'name': 'taxe', + 'caption': 'Taxes', + 'default': '0.00' + }, + { + 'name': 'modalite', + 'caption': '1x, 2x, 3x, xx, nx (if multiple separated by "/")', + 'default': '1x', + }, + { + 'name': 'moyen', + 'caption': ( + 'AUR, AMX, CBS, CGA, ' + 'CHK, DIN, PRE (if multiple separate by "/")' + ), + 'default': 'CBS', + }, + ] } devise = '978' def request(self, amount, name=None, address=None, email=None, phone=None, - orderid=None, info1=None, info2=None, info3=None, next_url=None, - logger=LOGGER, **kwargs): + orderid=None, info1=None, info2=None, info3=None, + next_url=None, logger=LOGGER, **kwargs): logger.debug('requesting spplus payment with montant %s email=%s' % (amount, email)) reference = self.transaction_id(20, ALPHANUM, 'spplus', self.siret) validite = dt.date.today()+dt.timedelta(days=1) validite = validite.strftime('%d/%m/%Y') - fields = { 'siret': self.siret, - 'devise': self.devise, - 'langue': self.langue, - 'taxe': self.taxe, - 'montant': str(Decimal(amount)), - REFERENCE: orderid or reference, - 'validite': validite, - 'version': '1', - 'modalite': self.modalite, - 'moyen': self.moyen } + fields = { + 'siret': self.siret, + 'devise': self.devise, + 'langue': self.langue, + 'taxe': self.taxe, + 'montant': str(Decimal(amount)), + REFERENCE: orderid or reference, + 'validite': validite, + 'version': '1', + 'modalite': self.modalite, + 'moyen': self.moyen, + } if email: fields['email'] = email normal_return_url = self.normal_return_url @@ -162,15 +199,14 @@ class Payment(PaymentCommon): "set normal_return_url in options", DeprecationWarning) normal_return_url = next_url if normal_return_url: - if (not normal_return_url.startswith('http://') \ - and not normal_return_url.startswith('https://')) \ - or '?' in normal_return_url: - raise ValueError('normal_return_url must be an absolute URL without parameters') + if ((not normal_return_url.startswith('http://') + and not normal_return_url.startswith('https://')) + or '?' in normal_return_url): + raise ValueError('normal_return_url must be an absolute URL without parameters') fields['urlretour'] = normal_return_url logger.debug('sending fields %s' % fields) query = urllib.urlencode(fields) - url = '%s?%s&hmac=%s' % (SERVICE_URL, query, sign_url_paiement(self.cle, - query)) + url = '%s?%s&hmac=%s' % (SERVICE_URL, query, sign_url_paiement(self.cle, query)) logger.debug('full url %s' % url) return reference, URL, url @@ -211,20 +247,20 @@ class Payment(PaymentCommon): elif etat in VALID_STATE: result = RECEIVED elif etat in TEST_STATE: - result = RECEIVED # what else ? + result = RECEIVED # what else ? test = True else: result = ERROR response = PaymentResponse( - result=result, - signed=signed, - bank_data=form, - order_id=reference, - transaction_id=form[self.BANK_ID], - bank_status=' - '.join(bank_status), - return_content=SPCHECKOK, - test=test) + result=result, + signed=signed, + bank_data=form, + order_id=reference, + transaction_id=form[self.BANK_ID], + bank_status=' - '.join(bank_status), + return_content=SPCHECKOK, + test=test) return response diff --git a/eopayment/systempayv2.py b/eopayment/systempayv2.py index 705d33d..49ff6fb 100644 --- a/eopayment/systempayv2.py +++ b/eopayment/systempayv2.py @@ -1,4 +1,19 @@ # -*- coding: utf-8 -*- +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . import pytz import datetime as dt @@ -286,7 +301,8 @@ class Payment(PaymentCommon): def request(self, amount, name=None, first_name=None, last_name=None, address=None, email=None, phone=None, orderid=None, info1=None, - info2=None, info3=None, next_url=None, manual_validation=None, **kwargs): + info2=None, info3=None, next_url=None, manual_validation=None, + **kwargs): ''' Create the URL string to send a request to SystemPay ''' @@ -332,8 +348,8 @@ class Payment(PaymentCommon): ptype = 'an-' p = Parameter(name, ptype, 13, max_length=32) if not p.check_value(orderid): - raise ValueError('%s value %s is not of the type %s' % (name, - orderid, ptype)) + raise ValueError( + '%s value %s is not of the type %s' % (name, orderid, ptype)) kwargs[name] = orderid transaction_id = self.transaction_id(6, string.digits, 'systempay', diff --git a/eopayment/tipi.py b/eopayment/tipi.py index 9a7e75b..ee30c50 100644 --- a/eopayment/tipi.py +++ b/eopayment/tipi.py @@ -1,3 +1,19 @@ +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + import re import random from decimal import Decimal, ROUND_DOWN diff --git a/tests/test_base_payment.py b/tests/test_base_payment.py index bb23959..d96f99f 100644 --- a/tests/test_base_payment.py +++ b/tests/test_base_payment.py @@ -1,13 +1,29 @@ +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + from datetime import date, datetime, timedelta import mock import pytest +import eopayment + def do_mock_backend(monkeypatch): - class MockBackend(object): - request = mock.Mock() description = { @@ -38,7 +54,6 @@ def do_mock_backend(monkeypatch): return MockBackend return backend - import eopayment monkeypatch.setattr(eopayment, 'get_backend', get_backend) return MockBackend, eopayment.Payment('kind', None) diff --git a/tests/test_dummy.py b/tests/test_dummy.py index 1725673..b46b1d8 100644 --- a/tests/test_dummy.py +++ b/tests/test_dummy.py @@ -1,24 +1,56 @@ +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + import eopayment import pytest + def test_dummy(): options = { - 'direct_notification_url': 'http://example.com/direct_notification_url', - 'siret': '1234', - 'origin': 'Mairie de Perpette-les-oies' + 'direct_notification_url': 'http://example.com/direct_notification_url', + 'siret': '1234', + 'origin': 'Mairie de Perpette-les-oies' } p = eopayment.Payment('dummy', options) - retour = 'http://example.com/retour?amount=10.0&direct_notification_url=http%3A%2F%2Fexample.com%2Fdirect_notification_url&email=toto%40example.com&transaction_id=6Tfw2e1bPyYnz7CedZqvdHt7T9XX6T&return_url=http%3A%2F%2Fexample.com%2Fretour&nok=1' - r = p.response(retour.split('?',1)[1]) + retour = ( + 'http://example.com/retour?amount=10.0' + '&direct_notification_url=http%3A%2F%2Fexample.com%2Fdirect_notification_url' + '&email=toto%40example.com' + '&transaction_id=6Tfw2e1bPyYnz7CedZqvdHt7T9XX6T' + '&return_url=http%3A%2F%2Fexample.com%2Fretour' + '&nok=1' + ) + r = p.response(retour.split('?', 1)[1]) assert not r.signed assert r.transaction_id == '6Tfw2e1bPyYnz7CedZqvdHt7T9XX6T' assert r.return_content is None - retour = 'http://example.com/retour?amount=10.0&direct_notification_url=http%3A%2F%2Fexample.com%2Fdirect_notification_url&email=toto%40example.com&transaction_id=6Tfw2e1bPyYnz7CedZqvdHt7T9XX6T&return_url=http%3A%2F%2Fexample.com%2Fretour&ok=1&signed=1' - r = p.response(retour.split('?',1)[1]) + retour = ( + 'http://example.com/retour' + '?amount=10.0' + '&direct_notification_url=http%3A%2F%2Fexample.com%2Fdirect_notification_url' + '&email=toto%40example.com' + '&transaction_id=6Tfw2e1bPyYnz7CedZqvdHt7T9XX6T' + '&return_url=http%3A%2F%2Fexample.com%2Fretour' + '&ok=1&signed=1' + ) + r = p.response(retour.split('?', 1)[1]) assert r.signed assert r.transaction_id == '6Tfw2e1bPyYnz7CedZqvdHt7T9XX6T' assert r.return_content == 'signature ok' - data = {'foo': 'bar'} with pytest.raises(eopayment.ResponseError, match='missing transaction_id'): p.response('foo=bar') diff --git a/tests/test_ogone.py b/tests/test_ogone.py index c44596d..abf472a 100644 --- a/tests/test_ogone.py +++ b/tests/test_ogone.py @@ -1,6 +1,23 @@ # -*- coding: utf-8 -*- +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . from unittest import TestCase +from xml.etree import ElementTree as ET + from six.moves.urllib import parse as urllib import eopayment @@ -17,17 +34,18 @@ BACKEND_PARAMS = { 'automatic_return_url': u'http://example.com/autömatic_réturn_url' } -class OgoneTests(TestCase): +class OgoneTests(TestCase): def test_request(self): ogone_backend = eopayment.Payment('ogone', BACKEND_PARAMS) amount = '42.42' order_id = u'my ordér' - reference, kind, what = ogone_backend.request(amount=amount, - orderid=order_id, email='foo@example.com') + reference, kind, what = ogone_backend.request( + amount=amount, + orderid=order_id, + email='foo@example.com') self.assertEqual(len(reference), 30) assert reference.startswith(order_id) - from xml.etree import ElementTree as ET root = ET.fromstring(str(what)) self.assertEqual(root.tag, 'form') self.assertEqual(root.attrib['method'], 'POST') @@ -67,27 +85,39 @@ class OgoneTests(TestCase): def test_iso_8859_1_response(self): ogone_backend = eopayment.Payment('ogone', BACKEND_PARAMS) order_id = 'lRXK4Rl1N2yIR3R5z7Kc' - backend_response = 'orderID=lRXK4Rl1N2yIR3R5z7Kc¤cy=EUR&amount=7%2E5&PM=CreditCard&ACCEPTANCE=test123&STATUS=9&CARDNO=XXXXXXXXXXXX9999&ED=0118&CN=Miha%EF+Serghe%EF&TRXDATE=10%2F24%2F16&PAYID=3011228911&NCERROR=0&BRAND=MasterCard&IP=80%2E12%2E92%2E47&SHASIGN=435D5E36E1F4B17739C1054FFD204218E65C15AB' + backend_response = ( + 'orderID=lRXK4Rl1N2yIR3R5z7Kc¤cy=EUR&amount=7%2E5' + '&PM=CreditCard&ACCEPTANCE=test123&STATUS=9' + '&CARDNO=XXXXXXXXXXXX9999&ED=0118' + '&CN=Miha%EF+Serghe%EF&TRXDATE=10%2F24%2F16' + '&PAYID=3011228911&NCERROR=0&BRAND=MasterCard' + '&IP=80%2E12%2E92%2E47&SHASIGN=435D5E36E1F4B17739C1054FFD204218E65C15AB' + ) response = ogone_backend.response(backend_response) assert response.signed self.assertEqual(response.order_id, order_id) def test_bad_response(self): ogone_backend = eopayment.Payment('ogone', BACKEND_PARAMS) - order_id = 'myorder' data = {'payid': '32100123', 'status': 9, 'ncerror': 0} with self.assertRaisesRegexp(ResponseError, 'missing ORDERID, PAYID, STATUS or NCERROR'): - response = ogone_backend.response(urllib.urlencode(data)) + ogone_backend.response(urllib.urlencode(data)) def test_bank_transfer_response(self): ogone_backend = eopayment.Payment('ogone', BACKEND_PARAMS) - order_id = 'myorder' - data = {'orderid': u'myorder', 'status': u'41', 'payid': u'3011229363', - 'cn': u'User', 'ncerror': u'0', - 'trxdate': u'10/24/16', - 'brand': 'Bank transfer', 'pm': 'bank transfer', - 'currency': u'eur', 'amount': u'7.5', - 'shasign': u'0E35F687ACBEAA6CA769E0ADDBD0863EB6C1678A'} + data = { + 'orderid': u'myorder', + 'status': u'41', + 'payid': u'3011229363', + 'cn': u'User', + 'ncerror': u'0', + 'trxdate': u'10/24/16', + 'brand': 'Bank transfer', + 'pm': 'bank transfer', + 'currency': u'eur', + 'amount': u'7.5', + 'shasign': u'0E35F687ACBEAA6CA769E0ADDBD0863EB6C1678A' + } # uniformize to utf-8 first for k in data: data[k] = eopayment.common.force_byte(data[k]) diff --git a/tests/test_paybox.py b/tests/test_paybox.py index e87b0b2..b504e04 100644 --- a/tests/test_paybox.py +++ b/tests/test_paybox.py @@ -1,4 +1,20 @@ # -*- coding: utf-8 -*- +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + import codecs from unittest import TestCase @@ -12,32 +28,45 @@ import eopayment.paybox as paybox import eopayment BACKEND_PARAMS = { - 'platform': u'test', - 'site': u'12345678', - 'rang': u'001', - 'identifiant': u'12345678', - 'shared_secret': u'0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF', - 'callback': u'http://example.com/callback', + 'platform': u'test', + 'site': u'12345678', + 'rang': u'001', + 'identifiant': u'12345678', + 'shared_secret': ( + u'0123456789ABCDEF0123456789ABCDEF01234' + u'56789ABCDEF0123456789ABCDEF0123456789' + u'ABCDEF0123456789ABCDEF0123456789ABCDE' + u'F0123456789ABCDEF' + ), + 'callback': u'http://example.com/callback', } + class PayboxTests(TestCase): def test_sign(self): - key = b'0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF' + key = (b'0123456789ABCDEF0123456789ABCDEF0123456789' + b'ABCDEF0123456789ABCDEF0123456789ABCDEF0123' + b'456789ABCDEF0123456789ABCDEF0123456789ABCD' + b'EF') key = codecs.decode(key, 'hex') d = dict(paybox.sign([ - ['PBX_SITE', u'12345678'], - ['PBX_RANG', u'32'], - ['PBX_IDENTIFIANT', u'12345678'], - ['PBX_TOTAL', u'999'], - ['PBX_DEVISE', u'978'], - ['PBX_CMD', u'appel à Paybox'], - ['PBX_PORTEUR', u'test@paybox.com'], - ['PBX_RETOUR', u'Mt:M;Ref:R;Auto:A;Erreur:E'], - ['PBX_HASH', u'SHA512'], - ['PBX_TIME', u'2015-06-08T16:21:16+02:00'], - ], - key)) - result = '7E74D8E9A0DBB65AAE51C5C50C2668FD98FC99AEDF18244BB1935F602B6C2E953B61FD84364F34FDB88B049901C0A07F6040AF446BBF5589113F48A733D551D4' + ['PBX_SITE', u'12345678'], + ['PBX_RANG', u'32'], + ['PBX_IDENTIFIANT', u'12345678'], + ['PBX_TOTAL', u'999'], + ['PBX_DEVISE', u'978'], + ['PBX_CMD', u'appel à Paybox'], + ['PBX_PORTEUR', u'test@paybox.com'], + ['PBX_RETOUR', u'Mt:M;Ref:R;Auto:A;Erreur:E'], + ['PBX_HASH', u'SHA512'], + ['PBX_TIME', u'2015-06-08T16:21:16+02:00'], + ], key)) + result = ( + '7E74D8E9A0DBB65AAE51C5C50C2668FD98FC99AED' + 'F18244BB1935F602B6C2E953B61FD84364F34FDB8' + '8B049901C0A07F6040AF446BBF5589113F48A733D' + '551D4' + ) self.assertIn('PBX_HMAC', d) self.assertEqual(d['PBX_HMAC'], result) @@ -61,14 +90,23 @@ class PayboxTests(TestCase): 'PBX_RANG': '01', 'PBX_SITE': '12345678', 'PBX_IDENTIFIANT': '12345678', - 'PBX_RETOUR': 'montant:M;reference:R;code_autorisation:A;erreur:E;numero_appel:T;numero_transaction:S;date_transaction:W;heure_transaction:Q;signature:K', + 'PBX_RETOUR': ( + 'montant:M;reference:R;code_autorisation:A;' + 'erreur:E;numero_appel:T;numero_transaction:S;' + 'date_transaction:W;heure_transaction:Q;signature:K' + ), 'PBX_TIME': time, 'PBX_PORTEUR': email, 'PBX_CMD': order_id + eopayment.common.ORDERID_TRANSACTION_SEPARATOR + transaction, 'PBX_TOTAL': amount.replace('.', ''), 'PBX_DEVISE': '978', 'PBX_HASH': 'SHA512', - 'PBX_HMAC': 'CE29AB421D9FF5E22B52A0F0D31BB881E6D3040B7A0B390AC3F335292A75D2389253A3ED6B3E430A90D30088F6AC29F792B484A2ECFC36A1B73771796A5FD15C', + 'PBX_HMAC': ( + 'CE29AB421D9FF5E22B52A0F0D31BB881E6D' + '3040B7A0B390AC3F335292A75D2389253A3' + 'ED6B3E430A90D30088F6AC29F792B484A2E' + 'CFC36A1B73771796A5FD15C' + ), 'PBX_ARCHIVAGE': '1234', 'PBX_REPONDRE_A': 'http://example.com/callback', 'PBX_AUTOSEULE': 'N' @@ -101,7 +139,8 @@ class PayboxTests(TestCase): transaction_id=transaction, time=time) root = ET.fromstring(str(what)) - form_params = dict(((node.attrib['name'], node.attrib['value']) for node in root if node.attrib['type'] == 'hidden')) + form_params = dict( + ((node.attrib['name'], node.attrib['value']) for node in root if node.attrib['type'] == 'hidden')) self.assertIn('PBX_DIFF', form_params) self.assertEqual(form_params['PBX_DIFF'], '07') @@ -109,13 +148,13 @@ class PayboxTests(TestCase): params = BACKEND_PARAMS.copy() backend = eopayment.Payment('paybox', params) transaction_id, kind, what = backend.request( - Decimal(amount), email=email, orderid=order_id, - transaction_id=transaction, time=time, capture_day=2) + Decimal(amount), email=email, orderid=order_id, + transaction_id=transaction, time=time, capture_day=2) root = ET.fromstring(str(what)) form_params = dict((( node.attrib['name'], node.attrib['value']) for node in root - if node.attrib['type'] == 'hidden')) + if node.attrib['type'] == 'hidden')) self.assertIn('PBX_DIFF', form_params) self.assertEqual(form_params['PBX_DIFF'], '02') @@ -125,17 +164,16 @@ class PayboxTests(TestCase): params['capture_day'] = '7' backend = eopayment.Payment('paybox', params) transaction_id, kind, what = backend.request( - Decimal(amount), email=email, orderid=order_id, - transaction_id=transaction, time=time, capture_day=2) + Decimal(amount), email=email, orderid=order_id, + transaction_id=transaction, time=time, capture_day=2) root = ET.fromstring(str(what)) form_params = dict((( node.attrib['name'], node.attrib['value']) for node in root - if node.attrib['type'] == 'hidden')) + if node.attrib['type'] == 'hidden')) self.assertIn('PBX_DIFF', form_params) self.assertEqual(form_params['PBX_DIFF'], '02') - def test_request_with_authorization_only(self): params = BACKEND_PARAMS.copy() time = '2018-08-21T10:26:32+02:00' @@ -151,7 +189,8 @@ class PayboxTests(TestCase): transaction_id=transaction, time=time) root = ET.fromstring(str(what)) - form_params = dict(((node.attrib['name'], node.attrib['value']) for node in root if node.attrib['type'] == 'hidden')) + form_params = dict( + ((node.attrib['name'], node.attrib['value']) for node in root if node.attrib['type'] == 'hidden')) self.assertEqual(form_params['PBX_AUTOSEULE'], 'O') def test_response(self): @@ -180,11 +219,16 @@ class PayboxTests(TestCase): params = BACKEND_PARAMS.copy() params['cle'] = 'cancelling_key' backend = eopayment.Payment('paybox', params) - bank_data = {'numero_transaction': ['13957441'], - 'numero_appel': ['30310733'], - 'reference': ['830657461681'] + bank_data = { + 'numero_transaction': ['13957441'], + 'numero_appel': ['30310733'], + 'reference': ['830657461681'], } - backend_raw_response = u"""NUMTRANS=0013989865&NUMAPPEL=0030378572&NUMQUESTION=0013989862&SITE=1999888&RANG=32&AUTORISATION=XXXXXX&CODEREPONSE=00000&COMMENTAIRE=Demande traitée avec succès&REFABONNE=&PORTEUR=""" + backend_raw_response = ( + u'NUMTRANS=0013989865&NUMAPPEL=0030378572&NUMQUESTION=0013989862' + u'&SITE=1999888&RANG=32&AUTORISATION=XXXXXX&CODEREPONSE=00000' + u'&COMMENTAIRE=Demande traitée avec succès&REFABONNE=&PORTEUR=' + ) backend_expected_response = {"CODEREPONSE": "00000", "RANG": "32", "AUTORISATION": "XXXXXX", @@ -206,17 +250,18 @@ class PayboxTests(TestCase): assert 'DATEQ' in params_sent # don't care about its value params_sent.pop('DATEQ') - expected_params = {'CLE': 'cancelling_key', - 'VERSION': '00103', - 'TYPE': operation_code, - 'MONTANT': Decimal('1000'), - 'NUMAPPEL': '30310733', - 'NUMTRANS': '13957441', - 'NUMQUESTION': '0013957441', - 'REFERENCE': '830657461681', - 'RANG': backend.backend.rang, - 'SITE': backend.backend.site, - 'DEVISE': backend.backend.devise + expected_params = { + 'CLE': 'cancelling_key', + 'VERSION': '00103', + 'TYPE': operation_code, + 'MONTANT': Decimal('1000'), + 'NUMAPPEL': '30310733', + 'NUMTRANS': '13957441', + 'NUMQUESTION': '0013957441', + 'REFERENCE': '830657461681', + 'RANG': backend.backend.rang, + 'SITE': backend.backend.site, + 'DEVISE': backend.backend.devise } self.assertEqual(params_sent, expected_params) self.assertEqual(backend_response, backend_expected_response) @@ -233,26 +278,33 @@ class PayboxTests(TestCase): error_response = u"""CODEREPONSE=00015&COMMENTAIRE=PAYBOX : Transaction non trouvée""" response = mock.Mock(status_code=200, text=error_response) requests_post.return_value = response - self.assertRaisesRegexp(eopayment.ResponseError, 'Transaction non trouvée', getattr(backend, operation_name), - Decimal('10'), bank_data) - + self.assertRaisesRegexp( + eopayment.ResponseError, + 'Transaction non trouvée', + getattr(backend, operation_name), + Decimal('10'), + bank_data) def test_validate_payment(self): params = BACKEND_PARAMS.copy() params['cle'] = 'cancelling_key' backend = eopayment.Payment('paybox', params) - bank_data = {'numero_transaction': ['13957441'], - 'numero_appel': ['30310733'], - 'reference': ['830657461681'] + bank_data = { + 'numero_transaction': ['13957441'], + 'numero_appel': ['30310733'], + 'reference': ['830657461681'] } - backend_raw_response = u"""NUMTRANS=0013989865&NUMAPPEL=0030378572&NUMQUESTION=0013989862&SITE=1999888&RANG=32&AUTORISATION=XXXXXX&CODEREPONSE=00000&COMMENTAIRE=Demande traitée avec succès&REFABONNE=&PORTEUR=""" + backend_raw_response = ( + u'NUMTRANS=0013989865&NUMAPPEL=0030378572&NUMQUESTION=0013989862' + u'&SITE=1999888&RANG=32&AUTORISATION=XXXXXX&CODEREPONSE=00000' + u'&COMMENTAIRE=Demande traitée avec succès&REFABONNE=&PORTEUR=' + ) with mock.patch('eopayment.paybox.requests.post') as requests_post: response = mock.Mock(status_code=200, text=backend_raw_response) requests_post.return_value = response backend.validate(Decimal(12), bank_data) - def test_rsa_signature_validation(self): pkey = '''-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDUgYufHuheMztK1LhQSG6xsOzb @@ -277,8 +329,11 @@ FBFKOZhgBJnkC+l6+XhT4aYWKaQ4ocmOMV92yjeXTE4=''' backend = eopayment.Payment('paybox', params) transaction_id, kind, what = backend.request( - Decimal(amount), email=email, orderid=order_id, - transaction_id=transaction, time=time) + Decimal(amount), + email=email, + orderid=order_id, + transaction_id=transaction, + time=time) root = ET.fromstring(str(what)) form_params = dict(( (node.attrib['name'], node.attrib['value']) for node in root @@ -287,8 +342,12 @@ FBFKOZhgBJnkC+l6+XhT4aYWKaQ4ocmOMV92yjeXTE4=''' self.assertEqual(form_params['PBX_AUTOSEULE'], 'N') transaction_id, kind, what = backend.request( - Decimal(amount), email=email, orderid=order_id, - transaction_id=transaction, time=time, manual_validation=True) + Decimal(amount), + email=email, + orderid=order_id, + transaction_id=transaction, + time=time, + manual_validation=True) root = ET.fromstring(str(what)) form_params = dict(( (node.attrib['name'], node.attrib['value']) for node in root diff --git a/tests/test_payfip_ws.py b/tests/test_payfip_ws.py index 5580081..d6909de 100644 --- a/tests/test_payfip_ws.py +++ b/tests/test_payfip_ws.py @@ -1,7 +1,7 @@ # coding: utf-8 # # eopayment - online payment library -# Copyright (C) 2011-2019 Entr'ouvert +# Copyright (C) 2011-2020 Entr'ouvert # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Affero General Public License as published diff --git a/tests/test_sips2.py b/tests/test_sips2.py index fe8c4d5..2c70882 100644 --- a/tests/test_sips2.py +++ b/tests/test_sips2.py @@ -1,4 +1,20 @@ # -*- coding: utf-8 -*- +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + import eopayment import pytest @@ -9,7 +25,7 @@ def test_build_request(): transaction, f, form = backend.request(amount=u'12', last_name=u'Foo', first_name=u'Félix000000') data = [f for f in form.fields if f['name'] == 'Data'] - assert not u'lix000000' in data[0]['value'] + assert u'lix000000' not in data[0]['value'] transaction, f, form = backend.request(amount=u'12') data = [f for f in form.fields if f['name'] == 'Data'] @@ -29,19 +45,20 @@ def test_options(): assert payment.backend.get_data()['captureMode'] == 'VALIDATION' payment = eopayment.Payment('sips2', {}) - assert not 'captureDay' in payment.backend.get_data() + assert 'captureDay' not in payment.backend.get_data() payment = eopayment.Payment('sips2', {'capture_day': '10'}) assert 'captureDay' in payment.backend.get_data() + 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=''' + 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=''' # noqa: E501 backend = eopayment.Payment('sips2', {}) response = backend.response(qs) assert response.signed assert response.transaction_date is None - 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%7CtransactionDateTime%3D2020-01-01%2001:01:01&Seal=6ca3247765a19b45d25ad54ef4076483e7d55583166bd5ac9c64357aac097602&InterfaceVersion=HP_2.0&Encode=''' + 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%7CtransactionDateTime%3D2020-01-01%2001:01:01&Seal=6ca3247765a19b45d25ad54ef4076483e7d55583166bd5ac9c64357aac097602&InterfaceVersion=HP_2.0&Encode=''' # noqa: E501 response = backend.response(qs) assert not response.signed assert response.transaction_date.isoformat() == '2020-01-01T01:01:01+01:00' diff --git a/tests/test_spplus.py b/tests/test_spplus.py index ba047e9..970a5db 100644 --- a/tests/test_spplus.py +++ b/tests/test_spplus.py @@ -1,14 +1,36 @@ +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + from unittest import TestCase import eopayment.spplus as spplus from eopayment import ResponseError + class SPPlustTest(TestCase): ntkey = b'58 6d fc 9c 34 91 9b 86 3f ' \ b'fd 64 63 c9 13 4a 26 ba 29 74 1e c7 e9 80 79' - tests = [('x=coin', 'c04f8266d6ae3ce37551cce996c751be4a95d10a'), - ('x=coin&y=toto', 'ef008e02f8dbf5e70e83da416b0b3a345db203de'), - ('x=wdwd%20%3Fdfgfdgd&z=343&hmac=04233b78bb5aff332d920d4e89394f505ec58a2a', '04233b78bb5aff332d920d4e89394f505ec58a2a')] + tests = [ + ('x=coin', + 'c04f8266d6ae3ce37551cce996c751be4a95d10a'), + ('x=coin&y=toto', + 'ef008e02f8dbf5e70e83da416b0b3a345db203de'), + ('x=wdwd%20%3Fdfgfdgd&z=343&hmac=04233b78bb5aff332d920d4e89394f505ec58a2a', + '04233b78bb5aff332d920d4e89394f505ec58a2a') + ] def test_spplus(self): payment = spplus.Payment({'cle': self.ntkey, 'siret': '00000000000001-01'}) diff --git a/tests/test_systempayv2.py b/tests/test_systempayv2.py index 248356e..6dcb29f 100644 --- a/tests/test_systempayv2.py +++ b/tests/test_systempayv2.py @@ -1,4 +1,21 @@ # -*- coding: utf-8 -*- +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + from datetime import datetime, timedelta import pytest @@ -25,14 +42,18 @@ def get_field(form, field_name): def test_systempayv2(): p = Payment(PARAMS) - data = {'amount': 15.24, 'orderid': '654321', - 'first_name': u'Jean Michél', - 'last_name': u'Mihaï' + data = { + 'amount': 15.24, + 'orderid': '654321', + 'first_name': u'Jean Michél', + 'last_name': u'Mihaï' } - qs = 'vads_version=V2&vads_page_action=PAYMENT&vads_action_mode=INTERACTIV' \ - 'E&vads_payment_config=SINGLE&vads_site_id=12345678&vads_ctx_mode=TES' \ - 'T&vads_trans_id=654321&vads_trans_date=20090501193530&vads_amount=15' \ - '24&vads_currency=978&vads_cust_first_name=Jean+Mich%C3%A9l&vads_cust_last_name=Mihaï' + qs = ( + 'vads_version=V2&vads_page_action=PAYMENT&vads_action_mode=INTERACTIV' + 'E&vads_payment_config=SINGLE&vads_site_id=12345678&vads_ctx_mode=TES' + 'T&vads_trans_id=654321&vads_trans_date=20090501193530&vads_amount=15' + '24&vads_currency=978&vads_cust_first_name=Jean+Mich%C3%A9l&vads_cust_last_name=Mihaï' + ) qs = urlparse.parse_qs(qs) for key in qs.keys(): qs[key] = qs[key][0] diff --git a/tests/test_tipi.py b/tests/test_tipi.py index 9e2660c..c0f194f 100644 --- a/tests/test_tipi.py +++ b/tests/test_tipi.py @@ -1,3 +1,19 @@ +# eopayment - online payment library +# Copyright (C) 2011-2020 Entr'ouvert +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + import datetime from decimal import Decimal from six.moves.urllib.parse import urlparse, parse_qs