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