2015-07-16 11:50:18 +02:00
|
|
|
|
# -*- coding: utf-8
|
2020-04-10 11:10:15 +02:00
|
|
|
|
# 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/>.
|
2015-06-08 16:26:29 +02:00
|
|
|
|
|
2018-03-26 09:56:16 +02:00
|
|
|
|
import codecs
|
2015-07-16 11:50:18 +02:00
|
|
|
|
from collections import OrderedDict
|
|
|
|
|
import datetime
|
2015-06-08 16:26:29 +02:00
|
|
|
|
import logging
|
|
|
|
|
import hashlib
|
|
|
|
|
import hmac
|
2020-10-11 15:25:51 +02:00
|
|
|
|
import re
|
2018-10-09 16:43:41 +02:00
|
|
|
|
import requests
|
2020-10-11 09:58:26 +02:00
|
|
|
|
import uuid
|
2020-04-04 12:32:57 +02:00
|
|
|
|
|
|
|
|
|
import pytz
|
|
|
|
|
|
2015-07-16 11:50:18 +02:00
|
|
|
|
from Crypto.Signature import PKCS1_v1_5
|
|
|
|
|
from Crypto.PublicKey import RSA
|
|
|
|
|
from Crypto.Hash import SHA
|
2018-03-26 09:56:16 +02:00
|
|
|
|
|
|
|
|
|
import six
|
|
|
|
|
from six.moves.urllib import parse as urlparse
|
|
|
|
|
from six.moves.urllib import parse as urllib
|
|
|
|
|
|
2015-07-16 11:50:18 +02:00
|
|
|
|
import base64
|
|
|
|
|
from gettext import gettext as _
|
2016-02-16 10:51:41 +01:00
|
|
|
|
import warnings
|
2015-07-16 11:50:18 +02:00
|
|
|
|
|
2020-10-11 15:25:51 +02:00
|
|
|
|
from .common import (PaymentCommon, PaymentResponse, FORM, PAID, CANCELLED,
|
|
|
|
|
DENIED, ERROR, Form, ResponseError, force_text,
|
2020-04-04 12:28:13 +02:00
|
|
|
|
force_byte)
|
2020-10-11 15:25:51 +02:00
|
|
|
|
from . import cb
|
2015-07-16 11:50:18 +02:00
|
|
|
|
|
|
|
|
|
__all__ = ['sign', 'Payment']
|
|
|
|
|
|
|
|
|
|
PAYBOX_KEY = '''-----BEGIN PUBLIC KEY-----
|
|
|
|
|
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDe+hkicNP7ROHUssGNtHwiT2Ew
|
|
|
|
|
HFrSk/qwrcq8v5metRtTTFPE/nmzSkRnTs3GMpi57rBdxBBJW5W9cpNyGUh0jNXc
|
|
|
|
|
VrOSClpD5Ri2hER/GcNrxVRP7RlWOqB1C03q4QYmwjHZ+zlM4OUhCCAtSWflB4wC
|
|
|
|
|
Ka1g88CjFwRw/PB9kwIDAQAB
|
|
|
|
|
-----END PUBLIC KEY-----'''
|
2015-06-08 16:26:29 +02:00
|
|
|
|
|
|
|
|
|
VARS = {
|
|
|
|
|
'PBX_SITE': 'Numéro de site (fourni par Paybox)',
|
|
|
|
|
'PBX_RANG': 'Numéro de rang (fourni par Paybox)',
|
|
|
|
|
'PBX_IDENTIFIANT': 'Identifiant interne (fourni par Paybox)',
|
|
|
|
|
'PBX_TOTAL': 'Montant total de la transaction',
|
|
|
|
|
'PBX_DEVISE': 'Devise de la transaction',
|
2020-04-04 12:28:13 +02:00
|
|
|
|
'PBX_CMD': 'Référence commande côté commerçant',
|
2015-06-08 16:26:29 +02:00
|
|
|
|
'PBX_PORTEUR': 'Adresse E - mail de l’acheteur',
|
|
|
|
|
'PBX_RETOUR': 'Liste des variables à retourner par Paybox',
|
|
|
|
|
'PBX_HASH': 'Type d’algorit hme de hachage pour le calcul de l’empreinte',
|
|
|
|
|
'PBX_TIME': 'Horodatage de la transaction',
|
|
|
|
|
'PBX_HMAC': 'Signature calculée avec la clé secrète',
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-16 11:50:18 +02:00
|
|
|
|
PAYBOX_ERROR_CODES = {
|
2020-10-11 15:25:51 +02:00
|
|
|
|
'00000': {'message': 'Paiement réalisé avec succés.', 'result': PAID},
|
|
|
|
|
'00001': {
|
|
|
|
|
'message': 'Demande annulée par l\'usager.',
|
|
|
|
|
'result': CANCELLED,
|
|
|
|
|
},
|
|
|
|
|
'001xx': {
|
|
|
|
|
'message': 'Paiement refusé par le centre d’autorisation [voir '
|
|
|
|
|
'§12.112.1 Codes réponses du centre d’autorisationCodes réponses du '
|
|
|
|
|
'centre d’autorisation]. En cas d’autorisation de la transaction par '
|
|
|
|
|
'le centre d’autorisation de la banque ou de l’établissement financier '
|
|
|
|
|
'privatif, le code erreur “00100” sera en fait remplacé directement '
|
|
|
|
|
'par “00000”.'
|
|
|
|
|
},
|
|
|
|
|
'00003': {
|
|
|
|
|
'message': 'Erreur Paybox. Dans ce cas, il est souhaitable de faire une '
|
|
|
|
|
'tentative sur le site secondaire FQDN tpeweb1.paybox.com.'
|
|
|
|
|
},
|
|
|
|
|
'00004': {'message': 'Numéro de porteur ou cryptogramme visuel invalide.'},
|
|
|
|
|
'00006': {'message': 'Accès refusé ou site/rang/identifiant incorrect.'},
|
|
|
|
|
'00008': {'message': 'Date de fin de validité incorrecte.'},
|
|
|
|
|
'00009': {'message': 'Erreur de création d’un abonnement.'},
|
|
|
|
|
'00010': {'message': 'Devise inconnue.'},
|
|
|
|
|
'00011': {'message': 'Montant incorrect.'},
|
|
|
|
|
'00015': {'message': 'Paiement déjà effectué.'},
|
|
|
|
|
'00016': {
|
|
|
|
|
'message': 'Abonné déjà existant (inscription nouvel abonné). Valeur '
|
|
|
|
|
'‘U’ de la variable PBX_RETOUR.'
|
|
|
|
|
},
|
|
|
|
|
'00021': {'message': 'Carte non autorisée.', 'result': DENIED},
|
|
|
|
|
'00029': {
|
|
|
|
|
'message': 'Carte non conforme. Code erreur renvoyé lors de la documentation de la variable « PBX_EMPREINTE ».'
|
|
|
|
|
},
|
|
|
|
|
'00030': {
|
|
|
|
|
'message': 'Temps d’attente > 15 mn par l’internaute/acheteur au niveau de la page de paiements.'
|
|
|
|
|
},
|
|
|
|
|
'00031': {'message': 'Réservé'},
|
|
|
|
|
'00032': {'message': 'Réservé'},
|
|
|
|
|
'00033': {
|
|
|
|
|
'message': 'Code pays de l’adresse IP du navigateur de l’acheteur non autorisé.',
|
|
|
|
|
'result': DENIED,
|
|
|
|
|
},
|
|
|
|
|
'00040': {
|
|
|
|
|
'message': 'Opération sans authentification 3-DSecure, bloquée par le filtre.',
|
|
|
|
|
'result': DENIED,
|
|
|
|
|
},
|
|
|
|
|
'99999': {
|
|
|
|
|
'message': 'Opération en attente de validation par l’émetteur du moyen de paiement.'
|
|
|
|
|
},
|
2015-07-16 11:50:18 +02:00
|
|
|
|
}
|
|
|
|
|
|
2015-06-08 16:26:29 +02:00
|
|
|
|
ALGOS = {
|
|
|
|
|
'SHA512': hashlib.sha512,
|
|
|
|
|
'SHA256': hashlib.sha256,
|
|
|
|
|
'SHA384': hashlib.sha384,
|
|
|
|
|
'SHA224': hashlib.sha224,
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-16 11:50:18 +02:00
|
|
|
|
URLS = {
|
|
|
|
|
'test':
|
|
|
|
|
'https://preprod-tpeweb.paybox.com/cgi/MYchoix_pagepaiement.cgi',
|
|
|
|
|
'prod':
|
|
|
|
|
'https://tpeweb.paybox.com/cgi/MYchoix_pagepaiement.cgi',
|
|
|
|
|
'backup':
|
|
|
|
|
'https://tpeweb1.paybox.com/cgi/MYchoix_pagepaiement.cgi',
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-09 16:43:41 +02:00
|
|
|
|
PAYBOX_DIRECT_URLS = {
|
|
|
|
|
'test': 'https://preprod-ppps.paybox.com/PPPS.php',
|
|
|
|
|
'prod': 'https://ppps.paybox.com/PPPS.php',
|
|
|
|
|
'backup': 'https://ppps1.paybox.com/PPPS.php'
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-24 10:36:51 +02:00
|
|
|
|
PAYBOX_DIRECT_CANCEL_OPERATION = '00055'
|
2018-10-09 16:43:41 +02:00
|
|
|
|
PAYBOX_DIRECT_VALIDATE_OPERATION = '00002'
|
|
|
|
|
|
2018-10-24 10:36:51 +02:00
|
|
|
|
PAYBOX_DIRECT_VERSION_NUMBER = '00103'
|
2018-10-09 16:43:41 +02:00
|
|
|
|
|
|
|
|
|
PAYBOX_DIRECT_SUCCESS_RESPONSE_CODE = '00000'
|
|
|
|
|
|
2018-08-20 15:55:02 +02:00
|
|
|
|
# payment modes
|
|
|
|
|
PAYMENT_MODES = {'AUTHOR_CAPTURE': 'O',
|
|
|
|
|
'IMMEDIATE': 'N'}
|
|
|
|
|
|
2015-07-16 11:50:18 +02:00
|
|
|
|
|
2015-06-08 16:26:29 +02:00
|
|
|
|
def sign(data, key):
|
|
|
|
|
'''Take a list of tuple key, value and sign it by building a string to
|
|
|
|
|
sign.
|
|
|
|
|
'''
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
algo = None
|
|
|
|
|
logger.debug('signature key %r', key)
|
|
|
|
|
logger.debug('signed data %r', data)
|
|
|
|
|
for k, v in data:
|
|
|
|
|
if k == 'PBX_HASH' and v in ALGOS:
|
|
|
|
|
algo = ALGOS[v]
|
|
|
|
|
break
|
|
|
|
|
assert algo, 'Missing or invalid PBX_HASH'
|
2018-03-26 09:56:16 +02:00
|
|
|
|
tosign = ['%s=%s' % (k, force_text(v)) for k, v in data]
|
2015-06-08 16:26:29 +02:00
|
|
|
|
tosign = '&'.join(tosign)
|
|
|
|
|
logger.debug('signed string %r', tosign)
|
2018-03-26 09:56:16 +02:00
|
|
|
|
tosign = tosign.encode('utf-8')
|
2015-06-08 16:26:29 +02:00
|
|
|
|
signature = hmac.new(key, tosign, algo)
|
|
|
|
|
return tuple(data) + (('PBX_HMAC', signature.hexdigest().upper()),)
|
|
|
|
|
|
2015-07-16 11:50:18 +02:00
|
|
|
|
|
|
|
|
|
def verify(data, signature, key=PAYBOX_KEY):
|
|
|
|
|
'''Verify signature using SHA1withRSA by Paybox'''
|
|
|
|
|
key = RSA.importKey(key)
|
2019-10-25 09:15:47 +02:00
|
|
|
|
h = SHA.new(force_byte(data))
|
2015-07-16 11:50:18 +02:00
|
|
|
|
verifier = PKCS1_v1_5.new(key)
|
|
|
|
|
return verifier.verify(h, signature)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Payment(PaymentCommon):
|
|
|
|
|
'''Paybox backend for eopayment.
|
|
|
|
|
|
|
|
|
|
If you want to handle Instant Payment Notification, you must pass
|
2016-02-16 10:51:41 +01:00
|
|
|
|
provide a automatic_return_url option specifying the URL of the
|
|
|
|
|
callback endpoint.
|
2015-07-16 11:50:18 +02:00
|
|
|
|
|
|
|
|
|
Email is mandatory to emit payment requests with paybox.
|
|
|
|
|
|
|
|
|
|
IP adresses to authorize:
|
|
|
|
|
IN OUT
|
|
|
|
|
test 195.101.99.73 195.101.99.76
|
|
|
|
|
production 194.2.160.66 194.2.122.158
|
|
|
|
|
backup 195.25.7.146 195.25.7.166
|
|
|
|
|
'''
|
2015-07-17 10:07:54 +02:00
|
|
|
|
callback = None
|
2015-07-16 11:50:18 +02:00
|
|
|
|
|
|
|
|
|
description = {
|
|
|
|
|
'caption': _('Paybox'),
|
|
|
|
|
'parameters': [
|
2016-02-16 10:51:41 +01:00
|
|
|
|
{
|
|
|
|
|
'name': 'normal_return_url',
|
|
|
|
|
'caption': _('Normal return URL'),
|
|
|
|
|
'default': '',
|
|
|
|
|
'required': False,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'automatic_return_url',
|
|
|
|
|
'caption': _('Automatic return URL'),
|
|
|
|
|
'required': False,
|
|
|
|
|
},
|
2015-07-16 11:50:18 +02:00
|
|
|
|
{
|
|
|
|
|
'name': 'platform',
|
|
|
|
|
'caption': _('Plateforme cible'),
|
|
|
|
|
'default': 'test',
|
2020-09-03 15:04:23 +02:00
|
|
|
|
'choices': (
|
|
|
|
|
('test', 'Test'),
|
|
|
|
|
('backup', 'Backup'),
|
|
|
|
|
('prod', 'Production'),
|
|
|
|
|
)
|
2015-07-16 11:50:18 +02:00
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'site',
|
|
|
|
|
'caption': _('Numéro de site'),
|
|
|
|
|
'required': True,
|
2020-04-04 12:28:13 +02:00
|
|
|
|
'validation': lambda x: isinstance(x, six.string_types)
|
|
|
|
|
and x.isdigit() and len(x) == 7,
|
2015-07-16 11:50:18 +02:00
|
|
|
|
},
|
2018-10-09 16:43:41 +02:00
|
|
|
|
{
|
|
|
|
|
'name': 'cle',
|
|
|
|
|
'caption': _('Site key'),
|
|
|
|
|
'required': False,
|
2020-04-04 12:28:13 +02:00
|
|
|
|
'validation': lambda x: isinstance(x, six.string_types),
|
2018-10-09 16:43:41 +02:00
|
|
|
|
},
|
2015-07-16 11:50:18 +02:00
|
|
|
|
{
|
|
|
|
|
'name': 'rang',
|
|
|
|
|
'caption': _('Numéro de rang'),
|
|
|
|
|
'required': True,
|
2020-04-04 12:28:13 +02:00
|
|
|
|
'validation': lambda x: isinstance(x, six.string_types)
|
2020-11-17 16:39:27 +01:00
|
|
|
|
and x.isdigit() and len(x) == 3,
|
2015-07-16 11:50:18 +02:00
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'identifiant',
|
|
|
|
|
'caption': _('Identifiant'),
|
|
|
|
|
'required': True,
|
2020-04-04 12:28:13 +02:00
|
|
|
|
'validation': lambda x: isinstance(x, six.string_types)
|
|
|
|
|
and x.isdigit() and (0 < len(x) < 10),
|
2015-07-16 11:50:18 +02:00
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'shared_secret',
|
2020-08-31 16:45:05 +02:00
|
|
|
|
'caption': _('Secret partagé (clé HMAC)'),
|
2020-04-04 12:28:13 +02:00
|
|
|
|
'validation': lambda x: isinstance(x, str)
|
2020-08-31 16:58:09 +02:00
|
|
|
|
and all(a.lower() in '0123456789abcdef' for a in x),
|
2015-07-16 11:50:18 +02:00
|
|
|
|
'required': True,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'devise',
|
|
|
|
|
'caption': _('Devise'),
|
|
|
|
|
'default': '978',
|
|
|
|
|
'choices': (
|
|
|
|
|
('978', 'Euro'),
|
|
|
|
|
),
|
|
|
|
|
},
|
2015-07-17 10:07:54 +02:00
|
|
|
|
{
|
|
|
|
|
'name': 'callback',
|
|
|
|
|
'caption': _('Callback URL'),
|
2016-02-16 10:51:41 +01:00
|
|
|
|
'deprecated': True,
|
2015-07-17 10:07:54 +02:00
|
|
|
|
},
|
2018-08-21 10:56:49 +02:00
|
|
|
|
{
|
|
|
|
|
'name': 'capture_day',
|
|
|
|
|
'caption': _('Nombre de jours pour un paiement différé'),
|
|
|
|
|
'default': '',
|
|
|
|
|
'required': False,
|
2020-04-04 12:28:13 +02:00
|
|
|
|
'validation': lambda x: isinstance(x, six.string_types)
|
|
|
|
|
and x.isdigit() and (1 <= len(x) <= 2)
|
2018-08-21 10:56:49 +02:00
|
|
|
|
},
|
2018-08-20 15:55:02 +02:00
|
|
|
|
{
|
|
|
|
|
'name': 'capture_mode',
|
|
|
|
|
'caption': _('Capture Mode'),
|
|
|
|
|
'default': 'IMMEDIATE',
|
|
|
|
|
'required': False,
|
2020-05-19 11:31:32 +02:00
|
|
|
|
'choices': list(PAYMENT_MODES)
|
2018-08-20 15:55:02 +02:00
|
|
|
|
},
|
2019-05-10 17:03:03 +02:00
|
|
|
|
{
|
|
|
|
|
'name': 'manual_validation',
|
|
|
|
|
'caption': 'Validation manuelle',
|
|
|
|
|
'type': bool,
|
|
|
|
|
'default': False,
|
|
|
|
|
'scope': 'transaction'
|
2020-04-04 12:32:57 +02:00
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
'name': 'timezone',
|
|
|
|
|
'caption': _('Default Timezone'),
|
|
|
|
|
'default': 'Europe/Paris',
|
|
|
|
|
'required': False,
|
2019-05-10 17:03:03 +02:00
|
|
|
|
}
|
2015-07-16 11:50:18 +02:00
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-11 09:58:26 +02:00
|
|
|
|
def make_pbx_cmd(self, guid, orderid=None, transaction_id=None):
|
|
|
|
|
if not transaction_id:
|
|
|
|
|
date = datetime.datetime.now(pytz.timezone(self.timezone)).strftime('%Y-%m-%dT%H%M%S')
|
|
|
|
|
transaction_id = '%s_%s' % (date, guid)
|
|
|
|
|
pbx_cmd = transaction_id
|
|
|
|
|
if orderid:
|
|
|
|
|
pbx_cmd += '!' + orderid
|
|
|
|
|
return pbx_cmd
|
|
|
|
|
|
2019-05-10 17:03:03 +02:00
|
|
|
|
def request(self, amount, email, name=None, orderid=None, manual_validation=None, **kwargs):
|
2015-07-16 11:50:18 +02:00
|
|
|
|
d = OrderedDict()
|
2018-03-26 09:56:16 +02:00
|
|
|
|
d['PBX_SITE'] = force_text(self.site)
|
2020-11-17 16:39:27 +01:00
|
|
|
|
d['PBX_RANG'] = force_text(self.rang).strip()[-3:]
|
2018-03-26 09:56:16 +02:00
|
|
|
|
d['PBX_IDENTIFIANT'] = force_text(self.identifiant)
|
2020-03-04 11:12:22 +01:00
|
|
|
|
d['PBX_TOTAL'] = self.clean_amount(amount)
|
2018-03-26 09:56:16 +02:00
|
|
|
|
d['PBX_DEVISE'] = force_text(self.devise)
|
2020-10-11 09:58:26 +02:00
|
|
|
|
guid = str(uuid.uuid4().hex)
|
|
|
|
|
transaction_id = d['PBX_CMD'] = self.make_pbx_cmd(guid=guid,
|
|
|
|
|
transaction_id=kwargs.get('transaction_id'),
|
|
|
|
|
orderid=orderid)
|
2018-03-26 09:56:16 +02:00
|
|
|
|
d['PBX_PORTEUR'] = force_text(email)
|
2020-04-04 12:32:57 +02:00
|
|
|
|
d['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'
|
|
|
|
|
)
|
2015-07-16 11:50:18 +02:00
|
|
|
|
d['PBX_HASH'] = 'SHA512'
|
2020-04-10 11:10:15 +02:00
|
|
|
|
d['PBX_TIME'] = kwargs.get('time') or (
|
|
|
|
|
force_text(datetime.datetime.utcnow().isoformat('T')).split('.')[0]
|
|
|
|
|
+ '+00:00')
|
2020-10-11 09:58:26 +02:00
|
|
|
|
d['PBX_ARCHIVAGE'] = orderid or guid
|
2016-02-16 10:51:41 +01:00
|
|
|
|
if self.normal_return_url:
|
|
|
|
|
d['PBX_EFFECTUE'] = self.normal_return_url
|
|
|
|
|
d['PBX_REFUSE'] = self.normal_return_url
|
|
|
|
|
d['PBX_ANNULE'] = self.normal_return_url
|
|
|
|
|
d['PBX_ATTENTE'] = self.normal_return_url
|
|
|
|
|
automatic_return_url = self.automatic_return_url
|
|
|
|
|
if not automatic_return_url and self.callback:
|
|
|
|
|
warnings.warn("callback option is deprecated, "
|
|
|
|
|
"use automatic_return_url", DeprecationWarning)
|
|
|
|
|
automatic_return_url = self.callback
|
2018-10-12 15:35:23 +02:00
|
|
|
|
capture_day = capture_day = kwargs.get('capture_day', self.capture_day)
|
|
|
|
|
if capture_day:
|
|
|
|
|
d['PBX_DIFF'] = capture_day.zfill(2)
|
2018-08-20 15:55:02 +02:00
|
|
|
|
d['PBX_AUTOSEULE'] = PAYMENT_MODES[self.capture_mode]
|
2019-05-10 17:03:03 +02:00
|
|
|
|
if manual_validation:
|
|
|
|
|
d['PBX_AUTOSEULE'] = PAYMENT_MODES['AUTHOR_CAPTURE']
|
2016-02-16 10:51:41 +01:00
|
|
|
|
if automatic_return_url:
|
2018-03-26 09:56:16 +02:00
|
|
|
|
d['PBX_REPONDRE_A'] = force_text(automatic_return_url)
|
2015-07-16 11:50:18 +02:00
|
|
|
|
d = d.items()
|
2018-03-26 09:56:16 +02:00
|
|
|
|
|
|
|
|
|
if six.PY3:
|
|
|
|
|
shared_secret = codecs.decode(bytes(self.shared_secret, 'ascii'), 'hex')
|
|
|
|
|
else:
|
|
|
|
|
shared_secret = codecs.decode(bytes(self.shared_secret), 'hex')
|
|
|
|
|
d = sign(d, shared_secret)
|
2015-07-16 11:50:18 +02:00
|
|
|
|
url = URLS[self.platform]
|
|
|
|
|
fields = []
|
|
|
|
|
for k, v in d:
|
|
|
|
|
fields.append({
|
|
|
|
|
'type': u'hidden',
|
2018-03-26 09:56:16 +02:00
|
|
|
|
'name': force_text(k),
|
|
|
|
|
'value': force_text(v),
|
2015-07-16 11:50:18 +02:00
|
|
|
|
})
|
|
|
|
|
form = Form(url, 'POST', fields, submit_name=None,
|
|
|
|
|
submit_value=u'Envoyer', encoding='utf-8')
|
|
|
|
|
return transaction_id, FORM, form
|
|
|
|
|
|
|
|
|
|
def response(self, query_string, callback=False, **kwargs):
|
|
|
|
|
d = urlparse.parse_qs(query_string, True, False)
|
2016-03-09 22:31:40 +01:00
|
|
|
|
if not set(d) >= set(['erreur', 'reference']):
|
2018-08-22 23:14:53 +02:00
|
|
|
|
raise ResponseError('missing erreur or reference')
|
2015-07-16 11:50:18 +02:00
|
|
|
|
signed = False
|
|
|
|
|
if 'signature' in d:
|
|
|
|
|
sig = d['signature'][0]
|
|
|
|
|
sig = base64.b64decode(sig)
|
|
|
|
|
data = []
|
|
|
|
|
if callback:
|
|
|
|
|
for key in ('montant', 'reference', 'code_autorisation',
|
2020-04-04 12:32:57 +02:00
|
|
|
|
'erreur', 'numero_appel', 'numero_transaction',
|
|
|
|
|
'date_transaction', 'heure_transaction'):
|
2015-07-16 11:50:18 +02:00
|
|
|
|
data.append('%s=%s' % (key, urllib.quote(d[key][0])))
|
|
|
|
|
else:
|
|
|
|
|
for key, value in urlparse.parse_qsl(query_string, True, True):
|
|
|
|
|
if key == 'signature':
|
|
|
|
|
break
|
|
|
|
|
data.append('%s=%s' % (key, urllib.quote(value)))
|
|
|
|
|
data = '&'.join(data)
|
|
|
|
|
signed = verify(data, sig)
|
2020-10-11 15:25:51 +02:00
|
|
|
|
erreur = d['erreur'][0]
|
|
|
|
|
if re.match(r'^001[0-9][0-9]$', erreur):
|
|
|
|
|
cb_error_code = erreur[3:5]
|
|
|
|
|
message, result = cb.translate_cb_error_code(cb_error_code)
|
|
|
|
|
elif erreur in PAYBOX_ERROR_CODES:
|
|
|
|
|
message = PAYBOX_ERROR_CODES[erreur]['message']
|
|
|
|
|
result = PAYBOX_ERROR_CODES[erreur].get('result', ERROR)
|
2015-07-16 11:50:18 +02:00
|
|
|
|
else:
|
2020-10-11 15:25:51 +02:00
|
|
|
|
message = 'Code erreur inconnu %s' % erreur
|
2015-07-16 11:50:18 +02:00
|
|
|
|
result = ERROR
|
2020-10-11 09:58:26 +02:00
|
|
|
|
pbx_cmd = d['reference'][0]
|
2020-04-04 12:32:57 +02:00
|
|
|
|
transaction_date = None
|
|
|
|
|
if 'date_transaction' in d and 'heure_transaction' in d:
|
|
|
|
|
try:
|
|
|
|
|
full_date_string = '%sT%s' % (d['date_transaction'][0], d['heure_transaction'][0])
|
|
|
|
|
transaction_date = datetime.datetime.strptime(full_date_string, '%Y%m%dT%H:%M:%S')
|
|
|
|
|
except ValueError:
|
|
|
|
|
pass
|
|
|
|
|
else:
|
|
|
|
|
# We suppose Europe/Paris is the default timezone for Paybox
|
|
|
|
|
# servers.
|
|
|
|
|
paris_tz = pytz.timezone(self.timezone)
|
|
|
|
|
transaction_date = paris_tz.localize(transaction_date)
|
2015-07-16 11:50:18 +02:00
|
|
|
|
return PaymentResponse(
|
2020-10-11 09:58:26 +02:00
|
|
|
|
order_id=pbx_cmd,
|
2015-07-16 11:50:18 +02:00
|
|
|
|
signed=signed,
|
|
|
|
|
bank_data=d,
|
|
|
|
|
result=result,
|
2020-10-11 15:25:51 +02:00
|
|
|
|
bank_status=message,
|
2020-04-04 12:32:57 +02:00
|
|
|
|
transaction_date=transaction_date)
|
2018-10-09 16:43:41 +02:00
|
|
|
|
|
|
|
|
|
def perform(self, amount, bank_data, operation):
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
url = PAYBOX_DIRECT_URLS[self.platform]
|
2020-04-04 12:28:13 +02:00
|
|
|
|
params = {
|
|
|
|
|
'VERSION': PAYBOX_DIRECT_VERSION_NUMBER,
|
|
|
|
|
'TYPE': operation,
|
|
|
|
|
'SITE': force_text(self.site),
|
|
|
|
|
'RANG': self.rang.strip(),
|
|
|
|
|
'CLE': force_text(self.cle),
|
|
|
|
|
'NUMQUESTION': bank_data['numero_transaction'][0].zfill(10),
|
2020-03-04 11:12:22 +01:00
|
|
|
|
'MONTANT': self.clean_amount(amount),
|
2020-04-04 12:28:13 +02:00
|
|
|
|
'DEVISE': force_text(self.devise),
|
|
|
|
|
'NUMTRANS': bank_data['numero_transaction'][0], # paybox transaction number
|
|
|
|
|
'NUMAPPEL': bank_data['numero_appel'][0],
|
|
|
|
|
'REFERENCE': bank_data['reference'][0],
|
|
|
|
|
'DATEQ': datetime.datetime.now().strftime('%d%m%Y%H%M%S'),
|
2018-10-09 16:43:41 +02:00
|
|
|
|
}
|
|
|
|
|
response = requests.post(url, params)
|
|
|
|
|
response.raise_for_status()
|
2019-10-29 15:30:38 +01:00
|
|
|
|
logger.debug('received %r', response.text)
|
|
|
|
|
data = dict(urlparse.parse_qsl(response.text, True, True))
|
2018-10-09 16:43:41 +02:00
|
|
|
|
if data.get('CODEREPONSE') != PAYBOX_DIRECT_SUCCESS_RESPONSE_CODE:
|
2018-10-25 17:49:10 +02:00
|
|
|
|
if six.PY2:
|
|
|
|
|
raise ResponseError(data['COMMENTAIRE'].encode('utf-8'))
|
|
|
|
|
raise ResponseError(data['COMMENTAIRE'])
|
2018-10-09 16:43:41 +02:00
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
def validate(self, amount, bank_data, **kwargs):
|
|
|
|
|
return self.perform(amount, bank_data, PAYBOX_DIRECT_VALIDATE_OPERATION)
|
|
|
|
|
|
|
|
|
|
def cancel(self, amount, bank_data, **kwargs):
|
|
|
|
|
return self.perform(amount, bank_data, PAYBOX_DIRECT_CANCEL_OPERATION)
|