From 111a62b8357158929d92383cb21cd52af4ed3a81 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Wed, 4 Mar 2015 17:19:50 +0100 Subject: [PATCH] Add new backend for Ogone --- eopayment/__init__.py | 1 + eopayment/ogone.py | 540 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 541 insertions(+) create mode 100644 eopayment/ogone.py diff --git a/eopayment/__init__.py b/eopayment/__init__.py index d00d1f5..4c23304 100644 --- a/eopayment/__init__.py +++ b/eopayment/__init__.py @@ -12,6 +12,7 @@ SYSTEMPAY = 'systempayv2' SPPLUS = 'spplus' TIPI = 'tipi' DUMMY = 'dummy' +OGONE = 'ogone' def get_backend(kind): diff --git a/eopayment/ogone.py b/eopayment/ogone.py new file mode 100644 index 0000000..daa822d --- /dev/null +++ b/eopayment/ogone.py @@ -0,0 +1,540 @@ +# -*- coding: utf-8 -*- +import hashlib +import string +import urlparse +from decimal import Decimal, ROUND_HALF_UP + +from common import (PaymentCommon, PaymentResponse, FORM, CANCELED, PAID, + ERROR, Form, DENIED, ACCEPTED) +def N_(message): return message + +ENVIRONMENT_TEST = 'TEST' +ENVIRONMENT_TEST_URL = 'https://secure.ogone.com/ncol/test/orderstandard.asp' +ENVIRONMENT_PROD = 'PROD' +ENVIRONMENT_PROD_URL = 'https://secure.ogone.com/ncol/prod/orderstandard.asp' +ENVIRONMENT = [ENVIRONMENT_TEST, ENVIRONMENT_PROD] + +SHA_IN_PARAMS = """ +ACCEPTANCE +ACCEPTURL +ADDMATCH +ADDRMATCH +AIACTIONNUMBER +AIAGIATA +AIAIRNAME +AIAIRTAX +AIBOOKIND*XX* +AICARRIER*XX* +AICHDET +AICLASS*XX* +AICONJTI +AIDEPTCODE +AIDESTCITY*XX* +AIDESTCITYL*XX* +AIEXTRAPASNAME*XX* +AIEYCD +AIFLDATE*XX* +AIFLNUM*XX* +AIGLNUM +AIINVOICE +AIIRST +AIORCITY*XX* +AIORCITYL*XX* +AIPASNAME +AIPROJNUM +AISTOPOV*XX* +AITIDATE +AITINUM +AITINUML*XX* +AITYPCH +AIVATAMNT +AIVATAPPL +ALIAS +ALIASOPERATION +ALIASPERSISTEDAFTERUSE +ALIASUSAGE +ALLOWCORRECTION +AMOUNT +AMOUNT*XX* +AMOUNTHTVA +AMOUNTTVA +ARP_TRN +BACKURL +BATCHID +BGCOLOR +BLVERNUM +BIC +BIN +BRAND +BRANDVISUAL +BUTTONBGCOLOR +BUTTONTXTCOLOR +CANCELURL +CARDNO +CATALOGURL +CAVV_3D +CAVVALGORITHM_3D +CERTID +CHECK_AAV +CIVILITY +CN +COM +COMPLUS +CONVCCY +COSTCENTER +COSTCODE +CREDITCODE +CREDITDEBIT +CUID +CURRENCY +CVC +CVCFLAG +DATA +DATATYPE +DATEIN +DATEOUT +DBXML +DCC_COMMPERC +DCC_CONVAMOUNT +DCC_CONVCCY +DCC_EXCHRATE +DCC_EXCHRATETS +DCC_INDICATOR +DCC_MARGINPERC +DCC_REF +DCC_SOURCE +DCC_VALID +DECLINEURL +DELIVERYDATE +DEVICE +DISCOUNTRATE +DISPLAYMODE +ECI +ECI_3D +ECOM_BILLTO_COMPANY +ECOM_BILLTO_POSTAL_CITY +ECOM_BILLTO_POSTAL_COUNTRYCODE +ECOM_BILLTO_POSTAL_COUNTY +ECOM_BILLTO_POSTAL_NAME_FIRST +ECOM_BILLTO_POSTAL_NAME_LAST +ECOM_BILLTO_POSTAL_NAME_PREFIX +ECOM_BILLTO_POSTAL_POSTALCODE +ECOM_BILLTO_POSTAL_STREET_LINE1 +ECOM_BILLTO_POSTAL_STREET_LINE2 +ECOM_BILLTO_POSTAL_STREET_LINE3 +ECOM_BILLTO_POSTAL_STREET_NUMBER +ECOM_BILLTO_TELECOM_MOBILE_NUMBER +ECOM_BILLTO_TELECOM_PHONE_NUMBER +ECOM_CONSUMERID +ECOM_CONSUMER_GENDER +ECOM_CONSUMEROGID +ECOM_CONSUMERORDERID +ECOM_CONSUMERUSERALIAS +ECOM_CONSUMERUSERPWD +ECOM_CONSUMERUSERID +ECOM_ESTIMATEDDELIVERYDATE +ECOM_ESTIMATEDELIVERYDATE +ECOM_PAYMENT_CARD_EXPDATE_MONTH +ECOM_PAYMENT_CARD_EXPDATE_YEAR +ECOM_PAYMENT_CARD_NAME +ECOM_PAYMENT_CARD_VERIFICATION +ECOM_SHIPMETHOD +ECOM_SHIPMETHODDETAILS +ECOM_SHIPMETHODSPEED +ECOM_SHIPMETHODTYPE +ECOM_SHIPTO_COMPANY +ECOM_SHIPTO_DOB +ECOM_SHIPTO_ONLINE_EMAIL +ECOM_SHIPTO_POSTAL_CITY +ECOM_SHIPTO_POSTAL_COUNTRYCODE +ECOM_SHIPTO_POSTAL_COUNTY +ECOM_SHIPTO_POSTAL_NAME_FIRST +ECOM_SHIPTO_POSTAL_NAME_LAST +ECOM_SHIPTO_POSTAL_NAME_PREFIX +ECOM_SHIPTO_POSTAL_POSTALCODE +ECOM_SHIPTO_POSTAL_STATE +ECOM_SHIPTO_POSTAL_STREET_LINE1 +ECOM_SHIPTO_POSTAL_STREET_LINE2 +ECOM_SHIPTO_POSTAL_STREET_NUMBER +ECOM_SHIPTO_TELECOM_FAX_NUMBER +ECOM_SHIPTO_TELECOM_MOBILE_NUMBER +ECOM_SHIPTO_TELECOM_PHONE_NUMBER +ECOM_SHIPTO_TVA +ED +EMAIL +EXCEPTIONURL +EXCLPMLIST +EXECUTIONDATE*XX* +FACEXCL*XX* +FACTOTAL*XX* +FIRSTCALL +FLAG3D +FONTTYPE +FORCECODE1 +FORCECODE2 +FORCECODEHASH +FORCEPROCESS +FORCETP +FP_ACTIV +GENERIC_BL +GIROPAY_ACCOUNT_NUMBER +GIROPAY_BLZ +GIROPAY_OWNER_NAME +GLOBORDERID +GUID +HDFONTTYPE +HDTBLBGCOLOR +HDTBLTXTCOLOR +HEIGHTFRAME +HOMEURL +HTTP_ACCEPT +HTTP_USER_AGENT +INCLUDE_BIN +INCLUDE_COUNTRIES +INITIAL_REC_TRN +INVDATE +INVDISCOUNT +INVLEVEL +INVORDERID +ISSUERID +IST_MOBILE +ITEM_COUNT +ITEMATTRIBUTES*XX* +ITEMCATEGORY*XX* +ITEMCOMMENTS*XX* +ITEMDESC*XX* +ITEMDISCOUNT*XX* +ITEMFDMPRODUCTCATEG*XX* +ITEMID*XX* +ITEMNAME*XX* +ITEMPRICE*XX* +ITEMQUANT*XX* +ITEMQUANTORIG*XX* +ITEMUNITOFMEASURE*XX* +ITEMVAT*XX* +ITEMVATCODE*XX* +ITEMWEIGHT*XX* +LANGUAGE +LEVEL1AUTHCPC +LIDEXCL*XX* +LIMITCLIENTSCRIPTUSAGE +LINE_REF +LINE_REF1 +LINE_REF2 +LINE_REF3 +LINE_REF4 +LINE_REF5 +LINE_REF6 +LIST_BIN +LIST_COUNTRIES +LOGO +MANDATEID +MAXITEMQUANT*XX* +MERCHANTID +MODE +MTIME +MVER +NETAMOUNT +OPERATION +ORDERID +ORDERSHIPCOST +ORDERSHIPMETH +ORDERSHIPTAX +ORDERSHIPTAXCODE +ORIG +OR_INVORDERID +OR_ORDERID +OWNERADDRESS +OWNERADDRESS2 +OWNERCTY +OWNERTELNO +OWNERTELNO2 +OWNERTOWN +OWNERZIP +PAIDAMOUNT +PARAMPLUS +PARAMVAR +PAYID +PAYMETHOD +PM +PMLIST +PMLISTPMLISTTYPE +PMLISTTYPE +PMLISTTYPEPMLIST +PMTYPE +POPUP +POST +PSPID +PSWD +RECIPIENTACCOUNTNUMBER +RECIPIENTDOB +RECIPIENTLASTNAME +RECIPIENTZIP +REF +REFER +REFID +REFKIND +REF_CUSTOMERID +REF_CUSTOMERREF +REGISTRED +REMOTE_ADDR +REQGENFIELDS +RNPOFFERT +RTIMEOUT +RTIMEOUTREQUESTEDTIMEOUT +SCORINGCLIENT +SEQUENCETYPE +SETT_BATCH +SID +SIGNDATE +STATUS_3D +SUBSCRIPTION_ID +SUB_AM +SUB_AMOUNT +SUB_COM +SUB_COMMENT +SUB_CUR +SUB_ENDDATE +SUB_ORDERID +SUB_PERIOD_MOMENT +SUB_PERIOD_MOMENT_M +SUB_PERIOD_MOMENT_WW +SUB_PERIOD_NUMBER +SUB_PERIOD_NUMBER_D +SUB_PERIOD_NUMBER_M +SUB_PERIOD_NUMBER_WW +SUB_PERIOD_UNIT +SUB_STARTDATE +SUB_STATUS +TAAL +TAXINCLUDED*XX* +TBLBGCOLOR +TBLTXTCOLOR +TID +TITLE +TOTALAMOUNT +TP +TRACK2 +TXTBADDR2 +TXTCOLOR +TXTOKEN +TXTOKENTXTOKENPAYPAL +TXSHIPPING +TXSHIPPINGLOCATIONPROFILE +TXURL +TXVERIFIER +TYPE_COUNTRY +UCAF_AUTHENTICATION_DATA +UCAF_PAYMENT_CARD_CVC2 +UCAF_PAYMENT_CARD_EXPDATE_MONTH +UCAF_PAYMENT_CARD_EXPDATE_YEAR +UCAF_PAYMENT_CARD_NUMBER +USERID +USERTYPE +VERSION +WBTU_MSISDN +WBTU_ORDERID +WEIGHTUNIT +WIN3DS +WITHROOT +""".split() + +SHA_OUT_PARAMS = """ +AAVADDRESS +AAVCHECK +AAVMAIL +AAVNAME +AAVPHONE +AAVZIP +ACCEPTANCE +ALIAS +AMOUNT +BIC +BIN +BRAND +CARDNO +CCCTY +CN +COLLECTOR_BIC +COLLECTOR_IBAN +COMPLUS +CREATION_STATUS +CREDITDEBIT +CURRENCY +CVCCHECK +DCC_COMMPERCENTAGE +DCC_CONVAMOUNT +DCC_CONVCCY +DCC_EXCHRATE +DCC_EXCHRATESOURCE +DCC_EXCHRATETS +DCC_INDICATOR +DCC_MARGINPERCENTAGE +DCC_VALIDHOURS +DEVICEID +DIGESTCARDNO +ECI +ED +EMAIL +ENCCARDNO +FXAMOUNT +FXCURRENCY +IP +IPCTY +MANDATEID +MOBILEMODE +NBREMAILUSAGE +NBRIPUSAGE +NBRIPUSAGE_ALLTX +NBRUSAGE +NCERROR +ORDERID +PAYID +PAYMENT_REFERENCE +PM +SCO_CATEGORY +SCORING +SEQUENCETYPE +SIGNDATE +STATUS +SUBBRAND +SUBSCRIPTION_ID +TRXDATE +VC""" + +class Payment(PaymentCommon): + # See http://payment-services.ingenico.com/fr/fr/ogone/support/guides/integration%20guides/e-commerce + description = { + 'caption': N_('Système de paiement Ogone / Ingenico Payment System e-Commerce'), + 'parameters': [ + {'name': 'environment', + 'default': ENVIRONMENT_TEST, + 'caption': N_(u'Environnement'), + 'choices': ENVIRONMENT, + }, + {'name': 'pspid', + 'caption': N_(u"Nom d'affiliation dans le système"), + 'required': True, + }, + {'name': 'language', + 'caption': N_(u'Langage'), + 'default': 'fr_FR', + 'choices': (('fr_FR', N_('français')),), + }, + {'name': 'hash_algorithm', + 'caption': N_(u'Algorithme de hachage'), + 'default': 'sha1', + }, + {'name': 'sha_in', + 'caption': N_(u'Clé SHA-IN'), + 'required': True, + }, + {'name': 'sha_out', + 'caption': N_(u'Clé SHA-OUT'), + 'required': True, + }, + {'name': 'currency', + 'caption': N_(u'Monnaie'), + 'default': 'EUR', + 'choices': ('EUR',), + }, + {'name': 'accepturl', + 'caption': N_(u'Monnaie'), + 'default': 'EUR', + 'choices': ('EUR',), + }, + ] + } + + def sha_sign(self, algo, key, params, keep): + '''Ogone signature algorithm of query string''' + values = params.items() + values = [(a.upper(), b) for a, b in values] + values = sorted(values) + values = ['%s=%s' % (a, b) for a, b in values if a in keep] + tosign = key.join(values) + tosign += key + hashing = getattr(hashlib, algo) + return hashing(tosign).hexdigest().upper() + + def sha_sign_in(self, params): + return self.sha_sign(self.hash_algorithm, self.sha_in, params, SHA_IN_PARAMS) + + def sha_sign_out(self, params): + return self.sha_sign(self.hash_algorithm, self.sha_out, params, SHA_OUT_PARAMS) + + def get_request_url(self): + if self.environment == ENVIRONMENT_TEST: + return ENVIRONMENT_TEST_URL + if self.environment == ENVIRONMENT_PROD: + return ENVIRONMENT_PROD_URL + raise NotImplementedError('unknown environment %s' % self.environment) + + def request(self, amount, orderid=None, name=None, email=None, + language=None, description=None, **kwargs): + reference = orderid or self.transaction_id(20, string.digits + string.ascii_letters) + language = language or self.language + # convertir en centimes + amount = Decimal(amount) * 100 + # arrondi comptable francais + amount = amount.quantize(Decimal('1.'), rounding=ROUND_HALF_UP) + params = { + 'AMOUNT': amount, + 'ORDERID': reference, + 'PSPID': self.pspid, + 'LANGUAGE': language, + 'CURRENCY': self.currency, + } + if name: + params['CN'] = name + if email: + params['EMAIL'] = email + if description: + params['COM'] = description + for key, value in kwargs.iteritems(): + params[key.upper()] = value + # uniformize all values to UTF-8 string + for key in params: + params[key] = unicode(params[key]).encode('utf-8') + params['SHASIGN'] = self.sha_sign_in(params) + url = self.get_request_url() + form = Form( + url=url, + method='POST', + fields=[{'type': 'hidden', + 'name': key, + 'value': params[key]} for key in params]) + return reference, FORM, form + + def response(self, query_string): + params = urlparse.parse_qs(query_string, True) + params = {key.upper(): params[key][0] for key in params} + reference = params['ORDERID'] + transaction_id = params['PAYID'] + status = params['STATUS'] + error = params['NCERROR'] + signed = False + if self.sha_in: + signature = params.get('SHASIGN') + expected_signature = self.sha_sign_out(params) + signed = signature == expected_signature + print 'signed', signature + print 'expected', expected_signature + if status == '1': + result = CANCELED + elif status == '2': + result = DENIED + elif status == '5': + result = ACCEPTED + elif status == '9': + result = PAID + else: + self.logger.error('response STATUS=%s NCERROR=%s NCERRORPLUS=%s', + status, error, params.get('NCERRORPLUS', '')) + result = ERROR + return PaymentResponse( + result=result, + signed=signed, + bank_data=params, + order_id=reference, + transaction_id=transaction_id)