From 153ae9cda8ba2936e658c29aa8205944bac14501 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Sun, 11 Oct 2020 09:43:07 +0200 Subject: [PATCH] systempayv2: map DENIED and CANCELLED result codes (#17065) --- eopayment/cb.py | 78 ++++++++++++--------- eopayment/systempayv2.py | 142 +++++++++++++++++++++----------------- tests/test_systempayv2.py | 10 ++- 3 files changed, 129 insertions(+), 101 deletions(-) diff --git a/eopayment/cb.py b/eopayment/cb.py index ccd7f69..6ab828d 100644 --- a/eopayment/cb.py +++ b/eopayment/cb.py @@ -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 diff --git a/eopayment/systempayv2.py b/eopayment/systempayv2.py index 6f6b869..8c7ab50 100644 --- a/eopayment/systempayv2.py +++ b/eopayment/systempayv2.py @@ -25,9 +25,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'] @@ -175,31 +175,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(): @@ -406,6 +381,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': 'L’adresse IP appartient à la liste grise du marchand.'}, + '06': {'message': 'Le code bin appartient à la liste grise du marchand.'}, + '07': {'message': 'Détection d’une e-carte bleue.'}, + '08': {'message': 'Détection d’une carte commerciale nationale.'}, + '09': {'message': 'Détection d’une carte commerciale étrangère.'}, + '14': {'message': 'Détection d’une carte à autorisation systématique.'}, + '30': {'message': 'Le pays de l’adresse IP appartient à la liste grise.'}, + '99': {'message': 'Problème technique recontré par le serveur lors du traitement ' + 'd\'un des contrôles locaux'}, + } + + @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_result in ('00', '05', '30') and vads_extra_result_message: + message += ' ' + vads_extra_result_message + if vads_auth_result_message: + message += ' ' + vads_auth_result_message + if result == ERROR and auth_eopayment_result not in (PAID, ERROR): + 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]): @@ -414,46 +455,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 @@ -467,7 +479,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 diff --git a/tests/test_systempayv2.py b/tests/test_systempayv2.py index e796360..3b64f60 100644 --- a/tests/test_systempayv2.py +++ b/tests/test_systempayv2.py @@ -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 -- 2.28.0