Add a new PaymentResponse to abstract the return value of the response method

This commit is contained in:
Benjamin Dauvergne 2012-02-17 17:49:51 +01:00
parent e9e3f0cc63
commit 8dee7ee341
5 changed files with 159 additions and 34 deletions

View File

@ -28,20 +28,23 @@ class PaymentResponse(object):
a response transmitted directly from the bank to the merchant website,
you usually have to confirm good reception of the message by returning a
properly formatted response, this is it.
bank_error_status -- if result is False, it contains the reason
bank_status -- if result is False, it contains the reason
order_id -- the id given by the merchant in the payment request
transaction_id -- the id assigned by the bank to this transaction, it
could be the one sent by the merchant in the request, but it is usually
an identifier internal to the bank.
'''
def __init__(self, result=None, signed_result=None, bank_data=dict(),
return_content=None, bank_error_status='', transaction_id=''):
return_content=None, bank_status='', transaction_id='',
order_id=''):
self.result = result
self.signed_result = signed_result
self.bank_data = bank_data
self.return_content = return_content
self.bank_error_status = bank_error_status
self.bank_status = bank_status
self.transaction_id = transaction_id
self.order_id = order_id
class PaymentCommon(object):
PATH = '/tmp'

View File

@ -5,7 +5,7 @@ try:
except:
from urlparse import parse_qs
from common import PaymentCommon, URL
from common import PaymentCommon, URL, PaymentResponse
__all__ = [ 'Payment' ]
@ -38,20 +38,31 @@ class Payment(PaymentCommon):
'dummy_service_url': {
'caption': 'URL of the dummy payment service',
'default': SERVICE_URL,
'type': string,
},
'direct_notification_url': {
'caption': 'direct notification url',
'type': string,
},
'origin': {
'caption': 'name of the requesting service, '
'to present in the user interface'
'to present in the user interface',
'type': string,
},
'siret': {
'caption': 'dummy siret parameter',
'type': string,
},
'next_url': {
'caption': 'Return URL for the user',
}
'type': string,
},
'consider_all_response_signed': {
'caption': '',
'type': bool,
'default': False,
},
}
}
@ -80,7 +91,15 @@ class Payment(PaymentCommon):
content = 'signature ok'
else:
content = None
return 'ok' in form and transaction_id and True, transaction_id, form, content
response = PaymentResponse(result='ok' in form,
signed_result='ok' in form and 'signed' in form,
bank_data=form,
return_content=content,
order_id=transaction_id,
transaction_id=transaction_id,
bank_status=form.get('reason'))
return response
if __name__ == '__main__':
options = {

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
import urlparse
import string
import subprocess
@ -7,7 +8,7 @@ import os
import os.path
import uuid
from common import PaymentCommon, HTML
from common import PaymentCommon, HTML, PaymentResponse
'''
Payment backend module for the ATOS/SIPS system used by many Frenck banks.
@ -61,6 +62,63 @@ DEFAULT_PARAMS = { 'merchant_id': '014213245611111',
LOGGER = logging.getLogger(__name__)
CB_BANK_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',
}
AMEX_BANK_RESPONSE_CODE = {
'00': 'Transaction approuvée ou traitée avec succès',
'02': 'Dépassement de plafond',
'04': 'Conserver la carte',
'05': 'Ne pas honorer',
'97': 'Échéance de la temporisation de surveillance globale',
}
FINAREF_BANK_RESPONSE_CODE = {
'00': 'Transaction approuvée',
'03': 'Commerçant inconnu - Identifiant de commerçant incorrect',
'05': 'Compte / Porteur avec statut bloqué ou invalide',
'11': 'Compte / porteur inconnu',
'16': 'Provision insuffisante',
'20': 'Commerçant invalide - Code monnaie incorrect - Opération commerciale inconnue - Opération commerciale invalide',
'80': 'Transaction approuvée avec dépassement',
'81': 'Transaction approuvée avec augmentation capital',
'82': 'Transaction approuvée NPAI',
'83': 'Compte / porteur invalide',
}
class Payment(PaymentCommon):
def __init__(self, options):
self.options = options
@ -106,6 +164,16 @@ class Payment(PaymentCommon):
result = self.execute('response', params)
d = dict(zip(RESPONSE_PARAMS, result))
# The reference identifier for the payment is the authorisation_id
d[self.BANK_ID] = result.get(AUTHORISATION_ID, '')
d[self.BANK_ID] = d.get(AUTHORISATION_ID)
LOGGER.debug('response contains fields %s' % d)
return result.get(RESPONSE_CODE) == '00', form.get(ORDER_ID), d, None
response_result = d.get(RESPONSE_CODE) == '00'
response_code_msg = CB_BANK_RESPONSE_CODES.get(d.get(RESPONSE_CODE))
response = PaymentResponse(
result=response_result,
signed_result=response_result,
bank_data=d,
order_id=d.get(ORDER_ID),
transaction_id=d.get(AUTHORISATION_ID),
bank_status=response_code_msg)
return None

View File

@ -10,7 +10,7 @@ import logging
import re
import Crypto.Cipher.DES
from common import PaymentCommon, URL
from common import PaymentCommon, URL, PaymentResponse
__all__ = ['Payment']
@ -122,21 +122,35 @@ next_url=%s' % (montant, email, next_url))
LOGGER.debug('received query_string %s' % query_string)
LOGGER.debug('parsed as %s' % form)
reference = form.get(REFERENCE)
if not 'hmac' in form:
return form.get('etat') == 1, reference, form, None
else:
bank_status = ''
signed_result = None
result = form.get('etat') == 1
form[self.BANK_ID] = form.get(REFSFP)
if 'hmac' in form:
try:
signed_data, signature = query_string.rsplit('&', 1)
_, hmac = signature.split('=', 1)
LOGGER.debug('got signature %s' % hmac)
computed_hmac = sign_ntkey_query(self.cle, signed_data)
LOGGER.debug('computed signature %s' % hmac)
result = hmac==computed_hmac \
result = signed_result = hmac==computed_hmac \
and form.get(ETAT) == ETAT_PAIEMENT_ACCEPTE
form[self.BANK_ID] = form.get(REFSFP, '')
return result, reference, form, SPCHECKOK
if not signed_result:
result = False
bank_status = 'invalid signature'
except ValueError:
return False, reference, form, SPCHECKOK
result = signed_result = False
response = PaymentResponse(
result=result,
signed_result=signed_result,
bank_data=form,
order_id=reference,
transaction_id=form[self.BANK_ID],
bank_status=bank_status,
return_content=SPCHECKOK if 'hmac' in form else None)
return response
if __name__ == '__main__':
import sys

View File

@ -8,7 +8,7 @@ import urlparse
import urllib
from decimal import Decimal
from common import PaymentCommon, URL
from common import PaymentCommon, URL, PaymentResponse
__all__ = ['Payment']
@ -17,6 +17,11 @@ LOGGER = logging.getLogger(__name__)
SERVICE_URL = '???'
VADS_TRANS_DATE = 'vads_trans_date'
VADS_AUTH_NUMBER = 'vads_auth_number'
VADS_AUTH_RESULT = 'vads_auth_result'
VADS_RESULT = 'vads_result'
VADS_EXTRA_RESULT = 'vads_extra_result'
SIGNATURE = 'signature'
VADS_TRANS_ID = 'vads_trans_id'
def isonow():
return dt.datetime.now() \
@ -232,7 +237,7 @@ parameters received: %s' % (name, kwargs))
raise TypeError('%s value %s is not of the type %s' % (
name, fields[name],
parameter.ptype))
fields['signature'] = self.signature(fields)
fields[SIGNATURE] = self.signature(fields)
url = '%s?%s' % (SERVICE_URL, urllib.urlencode(fields))
transaction_id = '%s_%s' % (fields[VADS_TRANS_DATE], transaction_id)
return transaction_id, URL, fields
@ -240,36 +245,52 @@ parameters received: %s' % (name, kwargs))
def response(self, query_string):
fields = urlparse.parse_qs(query_string)
copy = fields.copy()
if 'vads_auth_result' in fields:
v = copy['vads_auth_result']
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
if 'vads_result' in copy:
v = copy['vads_result']
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
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 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
fields['vads_extra_result'] = s
copy[VADS_EXTRA_RESULT] = s
bank_status.append(copy[VADS_EXTRA_RESULT])
elif v in ('05', '00'):
v = fields['vads_extra_result']
fields['vads_extra_result'] = '%s: %s' % (v,
v = fields[VADS_EXTRA_RESULT]
copy[VADS_EXTRA_RESULT] = '%s: %s' % (v,
EXTRA_RESULT_MAP.get(v, 'Code inconnu'))
bank_status.append(copy[VADS_EXTRA_RESULT])
LOGGER.debug('checking systempay response on:')
for key in sorted(fields.keys):
LOGGER.debug(' %s: %s' % (key, copy[key]))
signature = self.signature(fields)
result = signature == fields['signature']
signature_result = signature == fields[SIGNATURE]
if not signature_result:
bank_status.append('invalid signature')
result = fields[VADS_AUTH_RESULT] == '00'
signed_result = signature_result and result
LOGGER.debug('signature check result: %s' % result)
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
copy[self.BANK_ID] = copy.get(copy[VADS_AUTH_NUMBER], '')
return result, transaction_id, copy, None
copy[self.BANK_ID] = copy.get(VADS_AUTH_NUMBER, '')
response = PaymentResponse(
result=result,
signed_result=signed_result,
bankd_data=copy,
order_id=transaction_id,
transaction_id=copy.get(VADS_AUTH_NUMBER),
bank_status=' - '.join(bank_status))
return response
def signature(self, fields):
LOGGER.debug('got fields %s to sign' % fields )