systempayv2: map DENIED and CANCELLED result codes (#17065)

This commit is contained in:
Benjamin Dauvergne 2020-10-11 09:43:07 +02:00
parent b1ebd698b3
commit 7fa4e35641
3 changed files with 129 additions and 101 deletions

View File

@ -17,38 +17,50 @@
'''Responses codes emitted by EMV Card or 'Carte Bleu' in France'''
from . import PAID, CANCELLED, ERROR, DENIED
CB_RESPONSE_CODES = {
'00': 'Transaction approuvée ou traitée avec succès',
'02': 'Contacter l\'émetteur de carte',
'03': 'Accepteur invalide',
'04': 'Conserver la carte',
'05': 'Ne pas honorer',
'07': 'Conserver la carte, conditions spéciales',
'08': 'Approuver après identification',
'12': 'Transaction invalide',
'13': 'Montant invalide',
'14': 'Numéro de porteur invalide',
'15': 'Emetteur de carte inconnu',
'30': 'Erreur de format',
'31': 'Identifiant de l\'organisme acquéreur inconnu',
'33': 'Date de validité de la carte dépassée',
'34': 'Suspicion de fraude',
'41': 'Carte perdue',
'43': 'Carte volée',
'51': 'Provision insuffisante ou crédit dépassé',
'54': 'Date de validité de la carte dépassée',
'56': 'Carte absente du fichier',
'57': 'Transaction non permise à ce porteur',
'58': 'Transaction interdite au terminal',
'59': 'Suspicion de fraude',
'60': 'L\'accepteur de carte doit contacter l\'acquéreur',
'61': 'Dépasse la limite du montant de retrait',
'63': 'Règles de sécurité non respectées',
'68': 'Réponse non parvenue ou reçue trop tard',
'90': 'Arrêt momentané du système',
'91': 'Emetteur de cartes inaccessible',
'96': 'Mauvais fonctionnement du système',
'97': 'Échéance de la temporisation de surveillance globale',
'98': 'Serveur indisponible routage réseau demandé à nouveau',
'99': 'Incident domaine initiateur',
'00': {'message': 'Transaction approuvée ou traitée avec succès', 'result': PAID},
'02': {'message': 'Contacter l\'émetteur de carte'},
'03': {'message': 'Accepteur invalide'},
'04': {'message': 'Conserver la carte'},
'05': {'message': 'Ne pas honorer', 'result': DENIED},
'07': {'message': 'Conserver la carte, conditions spéciales'},
'08': {'message': 'Approuver après identification'},
'12': {'message': 'Transaction invalide'},
'13': {'message': 'Montant invalide'},
'14': {'message': 'Numéro de porteur invalide'},
'15': {'message': 'Emetteur de carte inconnu'},
'17': {'message': 'Annulation par l\'acheteur', 'result': CANCELLED},
'30': {'message': 'Erreur de format'},
'31': {'message': 'Identifiant de l\'organisme acquéreur inconnu'},
'33': {'message': 'Date de validité de la carte dépassée'},
'34': {'message': 'Suspicion de fraude'},
'41': {'message': 'Carte perdue'},
'43': {'message': 'Carte volée'},
'51': {'message': 'Provision insuffisante ou crédit dépassé'},
'54': {'message': 'Date de validité de la carte dépassée'},
'56': {'message': 'Carte absente du fichier'},
'57': {'message': 'Transaction non permise à ce porteur'},
'58': {'message': 'Transaction interdite au terminal'},
'59': {'message': 'Suspicion de fraude'},
'60': {'message': 'L\'accepteur de carte doit contacter l\'acquéreur'},
'61': {'message': 'Dépasse la limite du montant de retrait'},
'63': {'message': 'Règles de sécurité non respectées'},
'68': {'message': 'Réponse non parvenue ou reçue trop tard'},
'90': {'message': 'Arrêt momentané du système'},
'91': {'message': 'Emetteur de cartes inaccessible'},
'96': {'message': 'Mauvais fonctionnement du système'},
'97': {'message': 'Échéance de la temporisation de surveillance globale'},
'98': {'message': 'Serveur indisponible routage réseau demandé à nouveau'},
'99': {'message': 'Incident domaine initiateur'},
}
def translate_cb_error_code(error_code):
'Returns message, eopayment_error_code'
if error_code in CB_RESPONSE_CODES:
return CB_RESPONSE_CODES[error_code]['message'], CB_RESPONSE_CODES[error_code].get('result', ERROR)
return None, None

View File

@ -28,9 +28,9 @@ from six.moves.urllib import parse as urlparse
import warnings
from gettext import gettext as _
from .common import (PaymentCommon, PaymentResponse, PAID, ERROR, FORM, Form,
ResponseError, force_text, force_byte)
from .cb import CB_RESPONSE_CODES
from .common import (PaymentCommon, PaymentResponse, PAID, DENIED, CANCELLED,
ERROR, FORM, Form, ResponseError, force_text, force_byte)
from .cb import translate_cb_error_code
__all__ = ['Payment']
@ -179,31 +179,6 @@ PARAMETERS = [
PARAMETER_MAP = dict(((parameter.name,
parameter) for parameter in PARAMETERS))
AUTH_RESULT_MAP = CB_RESPONSE_CODES
RESULT_MAP = {
'00': 'paiement réalisé avec succés',
'02': 'le commerçant doit contacter la banque du porteur',
'05': 'paiement refusé',
'17': 'annulation client',
'30': 'erreur de format',
'96': 'erreur technique lors du paiement'
}
EXTRA_RESULT_MAP = {
'': "Pas de contrôle effectué",
'00': "Tous les contrôles se sont déroulés avec succés",
'02': "La carte a dépassé l'encours autorisé",
'03': "La carte appartient à la liste grise du commerçant",
'04': "Le pays d'émission de la carte appartient à la liste grise du "
"commerçant ou le pays d'émission de la carte n'appartient pas à la "
"liste blanche du commerçant",
'05': "L'addresse IP appartient à la liste grise du commerçant",
'99': "Problème technique recontré par le serveur lors du traitement "
"d'un des contrôles locaux",
}
def add_vads(kwargs):
new_vargs = {}
for k, v in kwargs.items():
@ -426,6 +401,72 @@ class Payment(PaymentCommon):
for field_name, field_value in fields.items()])
return transaction_id, FORM, form
RESULT_MAP = {
'00': {'message': 'Paiement réalisé avec succés.', 'result': PAID},
'02': {'message': 'Le commerçant doit contacter la banque du porteur.'},
'05': {'message': 'Paiement refusé.', 'result': DENIED},
'17': {'message': 'Annulation client.', 'result': CANCELLED},
'30': {'message': 'Erreur de format.'},
'96': {'message': 'Erreur technique lors du paiement.'},
}
EXTRA_RESULT_MAP = {
'': {'message': 'Pas de contrôle effectué.'},
'00': {'message': 'Tous les contrôles se sont déroulés avec succés.'},
'02': {'message': 'La carte a dépassé l\'encours autorisé.'},
'03': {'message': 'La carte appartient à la liste grise du commerçant.'},
'04': {'messaǵe': 'Le pays d\'émission de la carte appartient à la liste grise du '
'commerçant ou le pays d\'émission de la carte n\'appartient pas à la '
'liste blanche du commerçant.'},
'05': {'message': 'Ladresse IP appartient à la liste grise du marchand.'},
'06': {'message': 'Le code bin appartient à la liste grise du marchand.'},
'07': {'message': 'Détection dune e-carte bleue.'},
'08': {'message': 'Détection dune carte commerciale nationale.'},
'09': {'message': 'Détection dune carte commerciale étrangère.'},
'14': {'message': 'Détection dune carte à autorisation systématique.'},
'30': {'message': 'Le pays de ladresse IP appartient à la liste grise.'},
'99': {'message': 'Problème technique recontré par le serveur lors du traitement '
'd\'un des contrôles locauxi.'},
}
@classmethod
def make_eopayment_result(cls, fields):
# https://paiement.systempay.fr/doc/fr-FR/payment-file/oneclick-payment/vads-result.html
# https://paiement.systempay.fr/doc/fr-FR/payment-file/oneclick-payment/vads-auth-result.html
# https://paiement.systempay.fr/doc/fr-FR/payment-file/oneclick-payment/vads-extra-result.html
vads_result = fields.get(VADS_RESULT)
vads_auth_result = fields.get(VADS_AUTH_RESULT)
vads_extra_result = fields.get(VADS_EXTRA_RESULT)
# map to human messages and update return
vads_result_message = cls.RESULT_MAP.get(vads_result, {}).get('message')
if vads_result_message:
fields[VADS_RESULT + '_message'] = vads_result_message
vads_extra_result_message = cls.EXTRA_RESULT_MAP.get(vads_extra_result, {}).get('message')
if vads_extra_result_message:
fields[VADS_EXTRA_RESULT + '_message'] = vads_extra_result_message
vads_auth_result_message, auth_eopayment_result = translate_cb_error_code(vads_auth_result)
if vads_auth_result_message:
fields[VADS_AUTH_RESULT + '_message'] = vads_auth_result_message
# now build eopayment resume
if vads_result is None:
return ERROR, 'absence de champ vads_result'
if vads_result_message is None:
return ERROR, 'valeur vads_result inconnue'
result = cls.RESULT_MAP[vads_result].get('result', ERROR)
message = vads_result_message
if vads_auth_result_message and (vads_result != '00' or vads_result != vads_auth_result):
message += ' ' + vads_auth_result_message
if vads_result in ('00', '05', '30') and vads_extra_result_message and vads_extra_result != '':
message += ' ' + vads_extra_result_message
if result == ERROR and auth_eopayment_result not in (PAID, ERROR, None):
result = auth_eopayment_result
return result, message
def response(self, query_string, **kwargs):
fields = urlparse.parse_qs(query_string, True)
if not set(fields) >= set([SIGNATURE, VADS_CTX_MODE, VADS_AUTH_RESULT]):
@ -434,46 +475,17 @@ class Payment(PaymentCommon):
for key, value in fields.items():
fields[key] = value[0]
copy = fields.copy()
bank_status = []
if VADS_AUTH_RESULT in fields:
v = copy[VADS_AUTH_RESULT]
ctx = (v, AUTH_RESULT_MAP.get(v, 'Code inconnu'))
copy[VADS_AUTH_RESULT] = '%s: %s' % ctx
bank_status.append(copy[VADS_AUTH_RESULT])
if VADS_RESULT in copy:
v = copy[VADS_RESULT]
ctx = (v, RESULT_MAP.get(v, 'Code inconnu'))
copy[VADS_RESULT] = '%s: %s' % ctx
bank_status.append(copy[VADS_RESULT])
if v == '30':
if VADS_EXTRA_RESULT in fields:
v = fields[VADS_EXTRA_RESULT]
if v.isdigit():
for parameter in PARAMETERS:
if int(v) == parameter.code:
s = 'erreur dans le champ %s' % parameter.name
copy[VADS_EXTRA_RESULT] = s
bank_status.append(copy[VADS_EXTRA_RESULT])
elif v in ('05', '00'):
if VADS_EXTRA_RESULT in fields:
v = fields[VADS_EXTRA_RESULT]
extra_result_name = EXTRA_RESULT_MAP.get(v, 'Code inconnu')
copy[VADS_EXTRA_RESULT] = '%s: %s' % (v, extra_result_name)
bank_status.append(copy[VADS_EXTRA_RESULT])
self.logger.debug('checking systempay response on:')
for key in sorted(fields.keys()):
self.logger.debug(' %s: %s' % (key, copy[key]))
signature = self.signature(fields)
result, message = self.make_eopayment_result(copy)
self.logger.debug('checking systempay response on: %r', copy)
signature_result = signature == fields[SIGNATURE]
self.logger.debug('signature check: %s <!> %s', signature,
fields[SIGNATURE])
if not signature_result:
bank_status.append('invalid signature')
self.logger.debug('signature check: %s <!> %s', signature,
fields[SIGNATURE])
if not signature_result:
message += ' signature invalide.'
if fields[VADS_AUTH_RESULT] == '00':
result = PAID
else:
result = ERROR
test = fields[VADS_CTX_MODE] == 'TEST'
transaction_id = '%s_%s' % (copy[VADS_TRANS_DATE], copy[VADS_TRANS_ID])
# the VADS_AUTH_NUMBER is the number to match payment in bank logs
@ -487,7 +499,7 @@ class Payment(PaymentCommon):
bank_data=copy,
order_id=transaction_id,
transaction_id=copy.get(VADS_AUTH_NUMBER),
bank_status=' - '.join(bank_status),
bank_status=message,
transaction_date=transaction_date,
test=test)
return response

View File

@ -41,7 +41,8 @@ def get_field(form, field_name):
return field
def test_systempayv2():
def test_systempayv2(caplog):
caplog.set_level(0)
p = Payment(PARAMS)
data = {
'amount': 15.24,
@ -68,13 +69,14 @@ def test_systempayv2():
response_qs = 'vads_amount=1042&vads_auth_mode=FULL&vads_auth_number=3feadf' \
'&vads_auth_result=00&vads_capture_delay=0&vads_card_brand=CB' \
'&vads_result=00' \
'&vads_card_number=497010XXXXXX0000' \
'&vads_payment_certificate=582ba2b725057618706d7a06e9e59acdbf69ff53' \
'&vads_ctx_mode=TEST&vads_currency=978&vads_effective_amount=1042' \
'&vads_site_id=70168983&vads_trans_date=20161013101355' \
'&vads_trans_id=226787&vads_trans_uuid=4b5053b3b1fe4b02a07753e7a' \
'&vads_effective_creation_date=20200330162530' \
'&signature=faca0ef814d55a860996e28f84de9a9b29ddeca2'
'&signature=c17fab393f94dc027dc029510c85d5fc46c4710f'
response = p.response(response_qs)
assert response.result == PAID
assert response.signed
@ -85,6 +87,7 @@ def test_systempayv2():
p = Payment(PARAMS)
assert p.signature(qs) == 'aHrJ7IzSGFa4pcYA8kh99+M/xBzoQ4Odnu3f4BUrpIA='
response_qs = 'vads_amount=1042&vads_auth_mode=FULL&vads_auth_number=3feadf' \
'&vads_result=00' \
'&vads_auth_result=00&vads_capture_delay=0&vads_card_brand=CB' \
'&vads_card_number=497010XXXXXX0000' \
'&vads_payment_certificate=582ba2b725057618706d7a06e9e59acdbf69ff53' \
@ -92,8 +95,9 @@ def test_systempayv2():
'&vads_site_id=70168983&vads_trans_date=20161013101355' \
'&vads_trans_id=226787&vads_trans_uuid=4b5053b3b1fe4b02a07753e7a' \
'&vads_effective_creation_date=20200330162530' \
'&signature=PeU30M6ilqwhligBAIMQIR3yqxWFGZHJ8Hwtb%2B3IrOM%3D'
'&signature=Wbz3bP6R6wDvAwb2HnSiH9%2FiUUoRVCxK7mdLtCMz8Xw%3D'
response = p.response(response_qs)
assert response.result == PAID
assert response.signed
# bad response