From 4991a4834bd1e3f46325a985e12d0289d2c888c8 Mon Sep 17 00:00:00 2001 From: Aurlus Wedava Date: Tue, 13 Aug 2019 09:53:42 +0300 Subject: [PATCH] Paybox --- example_paybox/manage.py | 15 + example_paybox/paybox_test/__init__.py | 0 example_paybox/paybox_test/exceptions.py | 37 ++ example_paybox/paybox_test/paybox_direct.py | 282 +++++++++++ .../paybox_test/paybox_direct_plus.py | 289 ++++++++++++ example_paybox/paybox_test/paybox_system.py | 441 ++++++++++++++++++ example_paybox/test_payments/__init__.py | 0 example_paybox/test_payments/settings.py | 135 ++++++ example_paybox/test_payments/urls.py | 27 ++ example_paybox/test_payments/views.py | 102 ++++ example_paybox/test_payments/wsgi.py | 16 + 11 files changed, 1344 insertions(+) create mode 100755 example_paybox/manage.py create mode 100644 example_paybox/paybox_test/__init__.py create mode 100644 example_paybox/paybox_test/exceptions.py create mode 100644 example_paybox/paybox_test/paybox_direct.py create mode 100644 example_paybox/paybox_test/paybox_direct_plus.py create mode 100644 example_paybox/paybox_test/paybox_system.py create mode 100644 example_paybox/test_payments/__init__.py create mode 100644 example_paybox/test_payments/settings.py create mode 100644 example_paybox/test_payments/urls.py create mode 100644 example_paybox/test_payments/views.py create mode 100644 example_paybox/test_payments/wsgi.py diff --git a/example_paybox/manage.py b/example_paybox/manage.py new file mode 100755 index 0000000..8b39437 --- /dev/null +++ b/example_paybox/manage.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_payments.settings") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) diff --git a/example_paybox/paybox_test/__init__.py b/example_paybox/paybox_test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/example_paybox/paybox_test/exceptions.py b/example_paybox/paybox_test/exceptions.py new file mode 100644 index 0000000..88a88ae --- /dev/null +++ b/example_paybox/paybox_test/exceptions.py @@ -0,0 +1,37 @@ +class InvalidPaymentSolutionException(Exception): + """ + An exceptions raised when you attempt operations not allowed + on the payment solution in question. + e.g calling Paybox direct plus only operations on Paybox direct methods. + """ + def __init__(self, message, payload=None): + self.message = message + self.payload = payload + + def __str__(self): + return str(self.message) + + +class InvalidParametersException(Exception): + """ + An exceptions raised when Paybox has issues with parameters + passed for the call. + """ + def __init__(self, message, payload=None): + self.message = message + self.payload = payload + + def __str__(self): + return str(self.message) + + +class PayboxEndpointException(Exception): + """ + An exception raised when Paybox endpoints can't seem to be reached. + """ + def __init__(self, message, payload=None): + self.message = message + self.payload = payload + + def __str__(self): + return str(self.message) diff --git a/example_paybox/paybox_test/paybox_direct.py b/example_paybox/paybox_test/paybox_direct.py new file mode 100644 index 0000000..2293079 --- /dev/null +++ b/example_paybox/paybox_test/paybox_direct.py @@ -0,0 +1,282 @@ +import requests +from urllib import parse + +from .exceptions import InvalidPaymentSolutionException, InvalidParametersException, PayboxEndpointException +import urllib.parse +from time import sleep + + +from django.conf import settings + + +class PayboxDirectTransaction: + """A Paybox Direct transaction, from your server to Paybox server + Attributes: + REQUIRED The values nedded to call for a payment + OPTIONAL The values you may add to modify Paybox behavior + RESPONSE_CODES Every response code Paybox may return after a payment attempt + OPERATION_TYPES Codes for every type of operation possible + ACTIVITE This parameter allows to inform the acquirer (bank) how the transaction was initiated and how the card entry was realized. + """ + + def __init__(self, production=False, dateq=None, operation_type=None, numquestion=None, montant=None, + reference=None, refabonne=None, cle=None, devise=None, porteur=None, dateval=None, cvv=None, + activite=None, archivage=None, differe=None, numappel=None, numtrans=None, autorisation=None, + pays=None, priv_codetraitement=None, datenaiss=None, acquereur=None, typecarte=None, url_encode=None, + sha_1=None, errorcodetest=None, id_session=None, secure_3d=False, + url_http_direct=None): + + self.production = production + self.RETRY_CODES = ["00001", "00097", "00098"] + self.card_verification_api_redirect_url = url_http_direct + self.session_id = id_session + self.secure_3d = secure_3d + self.id3d = None + + self.ACTIVITE = { + "020": "Not specified", + "021": "Telephone order", + "022": "Mail Order", + "023": "Minitel(France)", + "024": "Internet payment", + "027": "Recurring payment" + } + + # VERSION=00104&TYPE=00001&SITE=1999888&RANG=32&CLE=1999888I&NUM + # QUESTION=194102418&MONTANT=1000&DEVISE=978&REFERENCE=TestPaybox&PORTEUR=1111222233334444&DATEVAL=0520&CVV=222&ACTIVITE=024&D + # ATEQ=30012013&PAYS= + + self.REQUIRED = { + "VERSION": "00104", # Version of the protocol PPPS used + "TYPE": operation_type, # Transaction operation type + "SITE": settings.PAYBOX_SITE, # SITE NUMBER (given by Paybox) 7 digits + "RANG": settings.PAYBOX_RANG, # RANG NUMBER (given by Paybox) 2 digits + "CLE": cle, # password for the merchant backoffice that is provided by the technical support upon the + # creation of the merchant account on the Paybox platform. + "NUMQUESTION": numquestion, # Unique identifier for the request that allows to avoid confusion + # in case of multiple simultaneous requests. + "MONTANT": montant, # Amount of the transaction in cents + "DEVISE": devise if devise else "978", # Currency code of the transaction + "REFERENCE": reference, # Merchant's reference number + "PORTEUR": porteur, # PAN (card number) of the customer, without any spaces and left aligned + "DATEVAL": dateval, # Expiry date of the card. (MMYY) + } + + self.OPTIONAL = { + "CVV": cvv, # Visual cryptogram on the back of the card. 3 0r 4 characters + "REFABONNE": refabonne, # Merchant reference number allowing him to clearly identify the + # subscriber (profile) that corresponds to the transaction. + "NUMAPPEL": numappel, # This number is returned by Verifone when a transaction is successfully processed + "NUMTRANS": numtrans, # This number is returned by Verifone when a transaction is successfully processed + "ACTIVITE": activite, # This parameter allows to inform the acquirer (bank) how the transaction + # was initiated and how the card entry was realized. + "DATEQ": dateq, # Date and time of the request using the format DDMMYYYYHHMMSS + "ARCHIVAGE": archivage, # This reference is transmitted to the acquirer (bank) of the merchant during + # the settlement of the transaction + "DIFFERE": differe, # Number of days to postpone the settlement + "AUTORISATION": autorisation, # Authorization number provided by the merchant that was obtained by + # telephone call to the acquirer (bank) + "PAYS": pays, # If the parameter is present (even empty), Paybox Direct returns the country + # code of issuance of the card in the response + "PRIV_CODETRAITEMENT": priv_codetraitement, # Parameter filled in by the merchant to indicate the + # payment option that is proposed to the cardholder of a SOFINCO card + # (or partner card of SOFINCO) or COFINOGA.3 digits. payment language. + # GBR for English + "DATENAISS": datenaiss, # Birthday of the cardholder for the cards of COFINOGA. Date(DDMMYYYY) + "ACQUEREUR": acquereur, # Defines the payment method used. The possible values are + # [PAYPAL, EMS, ATOSBE, BCMC, EQUENS, PSC, FINAREF, 34ONEY] + "TYPECARTE": typecarte, # If the parameter is present (even empty), Paybox Direct will return the type of + # card in the response (for a payment using with a card). + "URL_ENCODE": url_encode, # If the parameter contains O, Paybox Direct will URL-decode the value provided + # in each field before evaluating them. + "SHA-1": sha_1, # If the parameter is present (even empty), Paybox Direct will return the hash of the + # card in the response (for a payment with a card). + "ERRORCODETEST": errorcodetest, # The error code to return (forced) while doing integration testing in + # the pre-production environment. In production the parameter is not + # taken into account. + "ID3D": self.id3d, # Context identifier Verifone that holds the authentication result of the MPI + } + + self.RESPONSE_CODES = { + "00000": "Operation successful", + "00001": "Connection failed.", + "001xx": "Payment rejected", + "00002": "Error due to incoherence", + "00003": "Internal paybox error.", + "00004": "Card number invalid", + "00005": "Request number invalid", + "00006": "Site or rang invalid. Connection rejected", + "00007": "Date invalid", + "00008": "Card expiration date invalid", + "00009": "Requested operation invalid", + "00010": "Unrecognized currency", + "00011": "Incorrect amount", + "00012": "Order reference invalid", + "00013": "This version is no longer supported", + "00014": "Received request incoherent", + "00015": "Error accessing data previously referenced", + "00016": "Subscriber already exists", + "00017": "Subscriber does not exist", + "00018": "Transaction was not found", + "00019": "Reserved", + "00020": "Visual cryptogram missing(CVV)", + "00021": "Card not authorized", + "00022": "Threshold reached", + "00023": "Cardholder already seen", + "00024": "Country code filtered", + "00026": "Activity code incorrect", + "00040": "Card holder enrolled but not authenticated", + "00097": "Connection timeout", + "00098": "Internal connection timeout", + "00099": "Incoherence between query and reply", + } + + self.OPERATION_TYPES = { + "00001": "Authorization Only", + "00002": "Debit(Capture)", + "00003": "Authorization + Capture", + "00004": "Credit", + "00005": "Cancel", + "00011": "Check if a transaction exists", + "00012": "Transaction without authorization request", + "00014": "Refund", + "00017": "Inquiry" + } + + self.DIRECT_PLUS_ONLY_OPERATIONS = [ + "00051", "00052", "00053", "00054", "00055", "00056", "00057", "00058", "00061" + ] + + self.DELAY_OPERATIONS = [ + "00051", "00053" + ] + + def action_url(self): + if self.production: + main_url = "https://ppps.paybox.com/PPPS.php" + backup_url = "https://ppps1.paybox.com/PPPS.php" + request = requests.get(main_url) + if request.status_code == 200: + return main_url + else: + request = requests.get(backup_url) + if request.status_code == 200: + return backup_url + else: + main_url = "https://preprod-ppps.paybox.com/PPPS.php" + return main_url + raise PayboxEndpointException(message="Paybox Direct URL and its backup not responsive.") + + def remote_mpi_url(self): + if self.production: + mpi_url1 = "https://tpeweb.paybox.com/cgi/RemoteMPI.cgi" + mpi_url2 = "https://tpeweb1.paybox.com/cgi/RemoteMPI.cgi" + mpi_url3 = "https://tpeweb1.paybox.com/cgi/RemoteMPI.cgi" + mpi_url4 = "https://tpeweb0.paybox.com/cgi/RemoteMPI.cgi" + request = requests.get(mpi_url1) + if request.status_code == 200: + return mpi_url1 + else: + request = requests.get(mpi_url2) + if request.status_code == 200: + return mpi_url2 + else: + request = requests.get(mpi_url3) + if request.status_code == 200: + return mpi_url3 + else: + request = requests.get(mpi_url4) + if request.status_code == 200: + return mpi_url4 + else: + remote_mpi_url = "https://preprod-tpeweb.paybox.com/cgi/RemoteMPI.cgi" + return remote_mpi_url + raise PayboxEndpointException(message="Paybox MPI URL is not responsive.") + + def remote_mpi_authenticate(self, session_id): + """ + To carry out a 3D-Secure transaction, merchants will need to authenticate the cardholder before + calling Paybox Direct Applications + :return: {"ID3D": "", "StatusPBX": "", "Check": "", "IdSession": "", "3DCAVV": "", + "3DCAVVALGO": "", "3DECI": "", "3DENROLLED": "", "3DERROR": "", "3DSIGNVAL": "", + "3DSTATUS": "", "3DXID": "", "Check": ""} + """ + card_verification_string = "IdMerchant={0},IdSession={1},Amount={2},Currency={3},CCNumber={4},CCExpDate={5},CVVCode={6},URLHttpDirect={7}".format(settings.PAYBOX_IDENTIFIANT, session_id, self.REQUIRED['MONTANT'], self.REQUIRED['DEVISE'], self.REQUIRED['PORTEUR'], self.REQUIRED['DATEVAL'], self.OPTIONAL['CVV'], self.card_verification_api_redirect_url) + + remote_mpi_call = requests.post(self.remote_mpi_url(), data=card_verification_string) + response = dict(parse.parse_qsl(remote_mpi_call.text)) + try: + if urllib.parse.unquote(response['StatusPBX']) == "Autorisation à faire": + self.id3d = response['ID3D'] + elif urllib.parse.unquote(response['StatusPBX']) == "Autorisation à ne pas faire": + raise InvalidParametersException("Cardholder authentication failed.") + except KeyError: + raise InvalidParametersException(message="3D secure authentication failed.") + return response + + def post_to_paybox(self, numquestion, operation_type=None): + """ + To carry out a 3D-Secure transaction, merchants will need to authenticate the cardholder before + calling Paybox Direct Applications + :return: {"CODEREPONSE”": "", "COMMENTAIRE": "", "AUTORISATION": "", "NUMAPPEL": "" + "NUMQUESTION": "", "NUMTRANS": "", "PAYS": "", "PORTEUR": "", "RANG": "", + "REFABONNE": "", "REMISE": "", "SHA-1": "", "SITE": "", "STATUS": "", + "TYPECARTE": ""} + """ + self.REQUIRED['TYPE'] = operation_type + if self.secure_3d: + if self.id3d is None: + raise InvalidParametersException(message="3D Secure payments require mpi authentication.") + if operation_type in self.DIRECT_PLUS_ONLY_OPERATIONS: + raise InvalidPaymentSolutionException( + message="You have called a Paybox Direct Plus operation on a Paybox Direct method.") + if operation_type in self.DELAY_OPERATIONS: + sleep(1) + self.REQUIRED['NUMQUESTION'] = numquestion + payload = {**self.REQUIRED, **self.OPTIONAL} + session = requests.Session() + paybox_call = session.post(self.action_url(), data=payload) + response = dict(parse.parse_qsl(paybox_call.text)) + if response['CODEREPONSE'] in self.RETRY_CODES: + self.post_to_paybox(numquestion, operation_type) + if self.REQUIRED['TYPE'] == "00001": + self.OPTIONAL['NUMAPPEL'] = response['NUMAPPEL'] + self.OPTIONAL['NUMTRANS'] = response['NUMTRANS'] + return response + + def construct_html_form(self): + """ + Returns an html form ready to be POSTed to Paybox Direct (string) + :return: str
+ """ + + optional_fields = "\n".join( + [ + "".format( + field, self.OPTIONAL[field] + ) + for field in self.OPTIONAL + if self.OPTIONAL[field] + ] + ) + + html = """ + + + + + + + + + + + + + {optional} + +
""" + + return html.format( + action=self.action_url(), required=self.REQUIRED, optional=optional_fields + ) diff --git a/example_paybox/paybox_test/paybox_direct_plus.py b/example_paybox/paybox_test/paybox_direct_plus.py new file mode 100644 index 0000000..9fc50a4 --- /dev/null +++ b/example_paybox/paybox_test/paybox_direct_plus.py @@ -0,0 +1,289 @@ +from time import sleep +import requests +from urllib import parse +from .exceptions import InvalidPaymentSolutionException, InvalidParametersException, PayboxEndpointException +import urllib.parse +from django.conf import settings + + +class PayboxDirectPlusTransaction: + """A Paybox Direct Plus transaction, from your server to Paybox server + Attributes: + REQUIRED The values nedded to call for a payment + OPTIONAL The values you may add to modify Paybox behavior + RESPONSE_CODES Every response code Paybox may return after a payment attempt + OPERATION_TYPES Codes for every type of operation possible + ACTIVITE This parameter allows to inform the acquirer (bank) how the transaction was initiated and how the card entry was realized. + """ + + def __init__(self, production=False, dateq=None, operation_type=None, numquestion=None, montant=None, + reference=None, refabonne=None, cle=None, devise=None, porteur=None, dateval=None, cvv=None, + activite=None, archivage=None, differe=None, numappel=None, numtrans=None, autorisation=None, + pays=None, priv_codetraitement=None, datenaiss=None, acquereur=None, typecarte=None, url_encode=None, + sha_1=None, errorcodetest=None, id_merchant=None, id_session=None, url_http_direct=None, secure_3d=False): + + self.production = production + self.RETRY_CODES = ["00001", "00097", "00098"] + self.session_id = id_session + self.merchant_id = id_merchant + self.secure_3d = secure_3d + self.id3d = None + self.subscriber_registered = False + self.card_verification_api_redirect_url = url_http_direct + + self.ACTIVITE = { + "020": "Not specified", + "021": "Telephone order", + "022": "Mail Order", + "023": "Minitel(France)", + "024": "Internet payment", + "027": "Recurring payment" + } + + self.REQUIRED = { + "VERSION": "00104", # Version of the protocol PPPS used + "TYPE": operation_type, # Transaction operation type + "SITE": settings.PAYBOX_SITE, # SITE NUMBER (given by Paybox) 7 digits + "RANG": settings.PAYBOX_RANG, # RANG NUMBER (given by Paybox) 2 digits + "CLE": cle, # password for the merchant backoffice that is provided by the technical support upon the + # creation of the merchant account on the Paybox platform. + "NUMQUESTION": numquestion, # Unique identifier for the request that allows to avoid confusion + # in case of multiple simultaneous requests. + "MONTANT": montant, # Amount of the transaction in cents + "DEVISE": devise, # Currency code of the transaction + "REFERENCE": reference, # Merchant's reference number + "PORTEUR": porteur, # PAN (card number) of the customer, without any spaces and left aligned + "DATEVAL": dateval, # Expiry date of the card. (MMYY) + } + + self.OPTIONAL = { + "CVV": cvv, # Visual cryptogram on the back of the card. 3 0r 4 characters + "REFABONNE": refabonne, # Merchant reference number allowing him to clearly identify the + # subscriber (profile) that corresponds to the transaction. + "ACTIVITE": activite, # This parameter allows to inform the acquirer (bank) how the transaction + # was initiated and how the card entry was realized. + "DATEQ": dateq, # Date and time of the request using the format DDMMYYYYHHMMSS + "NUMAPPEL": numappel, # This number is returned by Verifone when a transaction is successfully processed + "NUMTRANS": numtrans, # This number is returned by Verifone when a transaction is successfully processed + "ARCHIVAGE": archivage, # This reference is transmitted to the acquirer (bank) of the merchant during + # the settlement of the transaction + "DIFFERE": differe, # Number of days to postpone the settlement + "AUTORISATION": autorisation, # Authorization number provided by the merchant that was obtained by + # telephone call to the acquirer (bank) + "PAYS": pays, # If the parameter is present (even empty), Paybox Direct returns the country + # code of issuance of the card in the response + "PRIV_CODETRAITEMENT": priv_codetraitement, # Parameter filled in by the merchant to indicate the + # payment option that is proposed to the cardholder of a SOFINCO card + # (or partner card of SOFINCO) or COFINOGA.3 digits. payment language. + # GBR for English + "DATENAISS": datenaiss, # Birthday of the cardholder for the cards of COFINOGA. Date(DDMMYYYY) + "ACQUEREUR": acquereur, # Defines the payment method used. The possible values are + # [PAYPAL, EMS, ATOSBE, BCMC, EQUENS, PSC, FINAREF, 34ONEY] + "TYPECARTE": typecarte, # If the parameter is present (even empty), Paybox Direct will return the type of + # card in the response (for a payment using with a card). + "URL_ENCODE": url_encode, # If the parameter contains O, Paybox Direct will URL-decode the value provided + # in each field before evaluating them. + "SHA-1": sha_1, # If the parameter is present (even empty), Paybox Direct will return the hash of the + # card in the response (for a payment with a card). + "ERRORCODETEST": errorcodetest, # The error code to return (forced) while doing integration testing in + # the pre-production environment. In production the parameter is not + # taken into account. + "ID3D": self.id3d, # Context identifier Verifone that holds the authentication result of the MPI + } + + self.RESPONSE_CODES = { + "00000": "Operation successful", + "00001": "Connection failed.", + "001xx": "Payment rejected", + "00002": "Error due to incoherence", + "00003": "Internal paybox error.", + "00004": "Card number invalid", + "00005": "Request number invalid", + "00006": "Site or rang invalid. Connection rejected", + "00007": "Date invalid", + "00008": "Card expiration date invalid", + "00009": "Requested operation invalid", + "00010": "Unrecognized currency", + "00011": "Incorrect amount", + "00012": "Order reference invalid", + "00013": "This version is no longer supported", + "00014": "Received request incoherent", + "00015": "Error accessing data previously referenced", + "00016": "Subscriber already exists", + "00017": "Subscriber does not exist", + "00018": "Transaction was not found", + "00019": "Reserved", + "00020": "Visual cryptogram missing(CVV)", + "00021": "Card not authorized", + "00022": "Threshold reached", + "00023": "Cardholder already seen", + "00024": "Country code filtered", + "00026": "Activity code incorrect", + "00040": "Card holder enrolled but not authenticated", + "00097": "Connection timeout", + "00098": "Internal connection timeout", + "00099": "Incoherence between query and reply", + } + + self.OPERATION_TYPES = { + "00051": "Authorization only on a subscriber", + "00052": "Debit on a subscriber", + "00053": "Authorization + Capture on a subscriber", + "00054": "Credit on a subscriber", + "00055": "Cancel subscriber transaction", + "00056": "Register new subscriber", + "00057": "Update an existing subscriber", + "00058": "Delete a subscriber", + "00061": "Authorization only", + } + + self.REGISTERED_USER_OPERATIONS = [ + "00051", "00052", "00054", "00055", "00057", "00058" + ] + + self.DELAY_OPERATIONS = [ + "00051", "00053" + ] + + def action_url(self): + if self.production: + main_url = "https://ppps.paybox.com/PPPS.php" + backup_url = "https://ppps1.paybox.com/PPPS.php" + request = requests.get(main_url) + if request.status_code == 200: + return main_url + else: + request = requests.get(backup_url) + if request.status_code == 200: + return backup_url + else: + main_url = "https://preprod-ppps.paybox.com/PPPS.php" + request = requests.get(main_url) + if request.status_code == 200: + return main_url + raise PayboxEndpointException(message="Paybox Direct URL and its backup not responsive.") + + def remote_mpi_url(self): + """ + This method returns a working remote_mpi_url to be used + for 3D-Secure transaction authentication + :return: str + """ + if self.production: + mpi_url1 = "https://tpeweb.paybox.com/cgi/RemoteMPI.cgi" + mpi_url2 = "https://tpeweb1.paybox.com/cgi/RemoteMPI.cgi" + mpi_url3 = "https://tpeweb1.paybox.com/cgi/RemoteMPI.cgi" + mpi_url4 = "https://tpeweb0.paybox.com/cgi/RemoteMPI.cgi" + request = requests.get(mpi_url1) + if request.status_code == 200: + return mpi_url1 + else: + request = requests.get(mpi_url2) + if request.status_code == 200: + return mpi_url2 + else: + request = requests.get(mpi_url3) + if request.status_code == 200: + return mpi_url3 + else: + request = requests.get(mpi_url4) + if request.status_code == 200: + return mpi_url4 + else: + remote_mpi_url = "https://preprod-tpeweb.paybox.com/cgi/RemoteMPI.cgi" + request = requests.get(remote_mpi_url) + if request.status_code == 200: + return remote_mpi_url + raise PayboxEndpointException(message="Paybox MPI URL is not responsive.") + + def remote_mpi_authenticate(self, session_id): + """ + To carry out a 3D-Secure transaction, merchants will need to authenticate the cardholder before + calling Paybox Direct Applications + :return: {"ID3D": "", "StatusPBX": "", "Check": "", "IdSession": "", "3DCAVV": "", + "3DCAVVALGO": "", "3DECI": "", "3DENROLLED": "", "3DERROR": "", "3DSIGNVAL": "", + "3DSTATUS": "", "3DXID": "", "Check": ""} + """ + card_verification_string = "IdMerchant={0},IdSession={1},Amount={2},Currency={3},CCNumber={4},CCExpDate={5},CVVCode={6},URLHttpDirect={7}".format( + settings.PAYBOX_IDENTIFIANT, session_id, self.REQUIRED['MONTANT'], self.REQUIRED['DEVISE'], + self.REQUIRED['PORTEUR'], self.REQUIRED['DATEVAL'], self.OPTIONAL['CVV'], + self.card_verification_api_redirect_url) + remote_mpi_call = requests.post(self.remote_mpi_url(), data=card_verification_string) + response = dict(parse.parse_qsl(remote_mpi_call.text)) + try: + if urllib.parse.unquote(response['StatusPBX']) == "Autorisation à faire": + self.id3d = response['ID3D'] + elif urllib.parse.unquote(response['StatusPBX']) == "Autorisation à ne pas faire": + raise InvalidParametersException("Cardholder authentication failed.") + except KeyError: + raise InvalidParametersException(message="3D secure authentication failed.") + return response + + def post_to_paybox(self, numquestion, operation_type=None): + """ + To carry out a 3D-Secure transaction, merchants will need to authenticate the cardholder before + calling Paybox Direct Applications + :return: {"CODEREPONSE”": "", "COMMENTAIRE": "", "AUTORISATION": "", "NUMAPPEL": "" + "NUMQUESTION": "", "NUMTRANS": "", "PAYS": "", "PORTEUR": "", "RANG": "", + "REFABONNE": "", "REMISE": "", "SHA-1": "", "SITE": "", "STATUS": "", + "TYPECARTE": ""} + """ + self.REQUIRED['TYPE'] = operation_type + self.REQUIRED['NUMQUESTION'] = numquestion + if self.secure_3d: + if self.id3d is None: + raise InvalidParametersException(message="3D Secure payments require mpi authentication.") + if self.REQUIRED['TYPE'] in self.REGISTERED_USER_OPERATIONS: + if not self.subscriber_registered: + raise InvalidPaymentSolutionException( + message="You need to be a registered subscriber to make this call.") + if self.REQUIRED['TYPE'] in self.DELAY_OPERATIONS: + sleep(1) + payload = {**self.REQUIRED, **self.OPTIONAL} + session = requests.Session() + paybox_call = session.post(self.action_url(), data=payload) + response = dict(parse.parse_qsl(paybox_call.text)) + if response['CODEREPONSE'] in self.RETRY_CODES: + self.post_to_paybox(numquestion, operation_type) + if self.REQUIRED['TYPE'] == "00056": + self.OPTIONAL['NUMAPPEL'] = response['NUMAPPEL'] + self.OPTIONAL['NUMTRANS'] = response['NUMTRANS'] + self.subscriber_registered = True + return response + + def construct_html_form(self): + """ + Returns an html form ready to be POSTed to Paybox Direct Plus(string) + :return: str
+ """ + + optional_fields = "\n".join( + [ + "".format( + field, self.OPTIONAL[field] + ) + for field in self.OPTIONAL + if self.OPTIONAL[field] + ] + ) + + html = """ + + + + + + + + + + + + + {optional} + +
""" + + return html.format( + action=self.action_url(), required=self.REQUIRED, optional=optional_fields + ) diff --git a/example_paybox/paybox_test/paybox_system.py b/example_paybox/paybox_test/paybox_system.py new file mode 100644 index 0000000..5f14c26 --- /dev/null +++ b/example_paybox/paybox_test/paybox_system.py @@ -0,0 +1,441 @@ +import base64 +import binascii +import datetime +import hashlib +import hmac +from urllib.parse import parse_qs, urlparse + +import requests + +from .exceptions import InvalidParametersException, PayboxEndpointException +from Crypto.Hash import SHA +from Crypto.PublicKey import RSA +from Crypto.Signature import PKCS1_v1_5 +from django.conf import settings + + +class PayboxSystemTransaction: + """A Paybox System transaction, from your server to the customer's browser, and from Paybox server to yours + Attributes: + REQUIRED The values nedded to call for a payment + OPTIONAL The values you may add to modify Paybox behavior + RESPONSE_CODES Every response code Paybox may return after a payment attempt + """ + + def __init__( + self, + production=False, + total=None, + cmd=None, + porteur=None, + time=None, + repondre_a=None, + refuse=None, + effectue=None, + annule=None, + attente=None, + langue=None, + hash=None, + devise=None, + subscription=False, + subscription_amount=None, + nbpaie=None, + freq=None, + quand=None, + delais=None, + several_payments=False, + mont1=None, + date1=None, + mont2=None, + date2=None, + mont3=None, + date3=None, + authorize_without_capture=False, + diff=None, + paybox_system_standard=False, + paybox_system_light=False, + paybox_system_mobile=False, + secure_3d=False, + card_verification_browser_redirect_url=None + ): + self.production = production + self.subscription = subscription + self.several_payments = several_payments + self.authorize_without_capture = authorize_without_capture + self.paybox_system_standard = paybox_system_standard, + self.paybox_system_light = paybox_system_light, + self.paybox_system_mobile = paybox_system_mobile + self.secure_3d = secure_3d + self.card_verification_api_redirect_url = card_verification_browser_redirect_url + + if self.production: + self.action_url = "https://tpeweb.e-transactions.fr/php/" + self.KEY = settings.PAYBOX_PUBLIC_KEY + else: + self.action_url = "https://preprod-tpeweb.e-transactions.fr/php/" + self.KEY = settings.PAYBOX_TEST_PUBLIC_KEY + + self.SUPPORTED_HASH_ALGORITHMS = [ + "SHA512", + "SHA224", + "SHA256", + "SHA384" + ] + + self.REQUIRED = { + "PBX_SITE": settings.PAYBOX_SITE, # SITE NUMBER (given by Paybox) + "PBX_RANG": settings.PAYBOX_RANG, # RANG NUMBER (given by Paybox) + "PBX_IDENTIFIANT": settings.PAYBOX_IDENTIFIANT, # IDENTIFIANT NUMBER (given by Paybox) + "PBX_TOTAL": total, # Total amount of the transaction, in cents + "PBX_DEVISE": devise if devise else "978", # Currency of the transaction + "PBX_CMD": cmd, # Transaction reference generated by the ecommerce + "PBX_PORTEUR": porteur, # Customer's email address + "PBX_RETOUR": "TO:M;RE:R;AU:A;RC:E;SIGN:K", # List of the variables Paybox must return to the IPN url + "PBX_HASH": hash if hash and hash in self.SUPPORTED_HASH_ALGORITHMS else "SHA512", # Hash algorithm used to calculate the Hmac value + "PBX_TIME": time if time else datetime.datetime.utcnow().isoformat(), # Time of the transaction (iso 8601 format) + } + + self.OPTIONAL = { + "PBX_REFUSE": refuse, # url de retour en cas de refus de paiement + "PBX_REPONDRE_A": repondre_a, # url IPN. WARNING. With Trailing slash, otherwise Django 301 to it... + "PBX_EFFECTUE": effectue, # url de retour en cas de succes + "PBX_ANNULE": annule, # url de retour en cas d'abandon + "PBX_ATTENTE": attente, # url de retour en cas d'abandon + "PBX_LANGUE": langue if langue else "GBR", # 3 Chars. payment language. GBR for English + "PBX_DIFF": diff + } + + self.SUBSCRIPTION = { + "PBX_2MONT": subscription_amount, # Amount of the next term of payment in cents. 0 takes amount in PBX_TOTAL + "PBX_NBPAIE": nbpaie, # Number of subsequent subscription payments. 0 is never ending + "PBX_FREQ": freq, # Frequency of payments in months + "PBX_QUAND": quand, # Day of month for for the payment to be executed + "PBX_DELAIS": delais, # Number of days to delay before fist payment is executed + } + + self.SEVERAL_PAYMENTS = { + "PBX_2MONT1": mont1, + "PBX_DATE1": date1, + "PBX_2MONT2": mont2, + "PBX_DATE2": date2, + "PBX_2MONT3": mont3, + "PBX_DATE3": date3 + } + + self.RESPONSE_CODES = { + "00000": "Success", + "00001": "Connection failed. Make a new attempt at tpeweb1.paybox.com", + "001xx": "Payment rejected", + "00003": "Paybox error. Make a new attempt at tpeweb1.paybox.com", + "00004": "Card number invalid", + "00006": "site, rang, or identifiant invalid. Connection rejected", + "00008": "Card expiration date invalid", + "00009": "Error while creating a subscription", + "00010": "Unrecognized currency", + "00011": "Incorrect amount", + "00015": "Payment already done", + "00016": "Subscriber already known", + "00021": "Unauthorized card", + "00029": "Incorrect card number", + "00030": "Time out", + "00031": "Reserved", + "00032": "Reserved", + "00033": "Country not supported", + "00040": "3DSecure validation failed", + "99999": "Payment on hold", + } + + self.PAYBOX_SYSTEM_MODES = [ + "paybox_system_standard", + "paybox_system_light", + "paybox_system_mobile" + ] + + def payment_mode(self): + try: + if settings.PAYBOX_SYSTEM_MODE: + if settings.PAYBOX_SYSTEM_MODE in self.PAYBOX_SYSTEM_MODES: + return settings.PAYBOX_SYSTEM_MODE + else: + if self.paybox_system_mobile: + return "paybox_system_mobile" + elif self.paybox_system_light: + return "paybox_system_light" + else: + return "paybox_system_standard" + except AttributeError: + return "paybox_system_standard" + + def endpoint_url(self): + payment_mode = self.payment_mode() + if payment_mode == "paybox_system_mobile": + if self.production: + main_url = "https://tpeweb.paybox.com/cgi/ChoixPaiementMobile.cgi" + backup_url = "https://tpeweb1.paybox.com/cgi/ChoixPaiementMobile.cgi" + request = requests.get(main_url) + if request.status_code == 200: + return main_url + else: + request = requests.get(backup_url) + if request.status_code == 200: + return backup_url + else: + return "https://preprod-tpeweb.paybox.com/cgi/ChoixPaiementMobile.cgi" + elif payment_mode == "paybox_system_light": + if self.production: + main_url = "https://tpeweb.paybox.com/cgi/MYframepagepaiement_ip.cgi" + backup_url = "https://tpeweb1.paybox.com/cgi/MYframepagepaiement_ip.cgi" + request = requests.get(main_url) + if request.status_code == 200: + return main_url + else: + request = requests.get(backup_url) + if request.status_code == 200: + return backup_url + else: + return "https://preprod-tpeweb.paybox.com/cgi/MYframepagepaiement_ip.cgi" + else: + if self.production: + main_url = "https://tpeweb.paybox.com/cgi/MYchoix_pagepaiement.cgi" + backup_url = "https://tpeweb1.paybox.com/cgi/MYchoix_pagepaiement.cgi" + request = requests.get(main_url) + if request.status_code == 200: + return main_url + else: + request = requests.get(backup_url) + if request.status_code == 200: + return backup_url + else: + return "https://preprod-tpeweb.paybox.com/cgi/MYchoix_pagepaiement.cgi" + + def remote_mpi_url(self): + if self.production: + mpi_url1 = "https://tpeweb.paybox.com/cgi/RemoteMPI.cgi" + mpi_url2 = "https://tpeweb1.paybox.com/cgi/RemoteMPI.cgi" + mpi_url3 = "https://tpeweb1.paybox.com/cgi/RemoteMPI.cgi" + mpi_url4 = "https://tpeweb0.paybox.com/cgi/RemoteMPI.cgi" + request = requests.get(mpi_url1) + if request.status_code == 200: + return mpi_url1 + else: + request = requests.get(mpi_url2) + if request.status_code == 200: + return mpi_url2 + else: + request = requests.get(mpi_url3) + if request.status_code == 200: + return mpi_url3 + else: + request = requests.get(mpi_url4) + if request.status_code == 200: + return mpi_url4 + else: + remote_mpi_url = "https://preprod-tpeweb.paybox.com/cgi/RemoteMPI.cgi" + return remote_mpi_url + raise PayboxEndpointException(message="Paybox MPI URL is not responsive.") + + def remote_mpi_authenticate(self, session_id): + """ + To carry out a 3D-Secure transaction, merchants will need to authenticate the cardholder before + calling Paybox Direct Applications + :return: {"ID3D": "", "StatusPBX": "", "Check": "", "IdSession": "", "3DCAVV": "", + "3DCAVVALGO": "", "3DECI": "", "3DENROLLED": "", "3DERROR": "", "3DSIGNVAL": "", + "3DSTATUS": "", "3DXID": "", "Check": ""} + """ + card_verification_params = { + "IdSession": session_id, + "IdMerchant": settings.PAYBOX_IDENTIFIANT, + "URLRetour": self.card_verification_api_redirect_url, + "Amount": self.REQUIRED['MONTANT'], + "Currency": self.REQUIRED['DEVISE'], + "CCExpDate": self.REQUIRED['DATEVAL'], + "CCNumber": self.REQUIRED['PORTEUR'], + "CVVCode": self.OPTIONAL['CVV'] + } + remote_mpi_call = requests.post(self.remote_mpi_url(), data=card_verification_params) + return remote_mpi_call.text + + def post_to_paybox(self): + """ + Returns the Paybox action url, required request variables, and the + optional variables + :return: { + "action": self.action_url, + "required": self.required, + "optional": self.optional, + } + """ + if self.secure_3d: + if self.id3d is None: + raise InvalidParametersException(message="3D Secure payments require mpi authentication.") + if self.REQUIRED["PBX_HASH"] not in self.SUPPORTED_HASH_ALGORITHMS: + raise InvalidParametersException(message="Unsupported hash algorithm provided.") + self.REQUIRED["PBX_DEVISE"] = self.REQUIRED.get("devise", "978") + if self.authorize_without_capture: + self.OPTIONAL["PBX_AUTOSEULE"] = 'O' + # string to sign. Made of the required variables in a precise order. + tosign = ( + "PBX_SITE=%(PBX_SITE)s&PBX_RANG=%(PBX_RANG)s&PBX_IDENTIFIANT=%(PBX_IDENTIFIANT)s&PBX_TOTAL=%(PBX_TOTAL)s&PBX_DEVISE=%(PBX_DEVISE)s&PBX_CMD=%(PBX_CMD)s&PBX_PORTEUR=%(PBX_PORTEUR)s&PBX_RETOUR=%(PBX_RETOUR)s&PBX_HASH=%(PBX_HASH)s&PBX_TIME=%(PBX_TIME)s" + % self.REQUIRED + ) + # Subscription variables + if self.subscription: + for key, value in self.SUBSCRIPTION.items(): + if value: + tosign += "&" + key + "=" + value + # Several payments + if self.several_payments: + for key, value in self.SEVERAL_PAYMENTS.items(): + if value: + tosign += "&" + key + "=" + value + # Optional variables + for key, value in self.OPTIONAL.items(): + if value: + tosign += "&" + key + "=" + value + + binary_key = binascii.unhexlify(self.KEY) + algorithms = { + "SHA512": hashlib.sha512, + "SHA224": hashlib.sha224, + "SHA256": hashlib.sha256, + "SHA384": hashlib.sha384 + } + chosen_algorithm = algorithms.get(self.REQUIRED['PBX_HASH']) + signature = ( + hmac.new(binary_key, tosign.encode("ascii"), chosen_algorithm) + .hexdigest() + .upper() + ) + self.REQUIRED["hmac"] = signature + + return { + "action": self.endpoint_url(), + "required": self.REQUIRED, + "optional": self.OPTIONAL, + } + + def construct_html_form(self): + """ Returns an html form ready to be used (string) + """ + subscription_fields = "\n".join( + [ + "".format( + field, self.SUBSCRIPTION[field] + ) + for field in self.SUBSCRIPTION + if self.SUBSCRIPTION[field] + ] + ) + + optional_fields = "\n".join( + [ + "".format( + field, self.OPTIONAL[field] + ) + for field in self.OPTIONAL + if self.OPTIONAL[field] + ] + ) + + html = """
+ + + + + + + + + + + + {subscription} + {optional} + +
""" + + return html.format( + action=self.endpoint_url(), required=self.REQUIRED, optional=optional_fields, + subscription=subscription_fields + ) + + def verify_ipn(self, response_url, total, verify_certificate=True): + """ Verifies the notification sent by Paybox to your server. + It verifies : + - the authenticity of the message + - the fact that the message has not been altered + - if not in production, the auth_number must be "XXXXXX" + - if in production, there must be a Response Code + - the total returned must be equal to the total of the order you've saved in ddb + :response_url: (string), the full response url with its encoded args + :order_total': (int), the total amount required + :verify_certificate: (bool) + It returns a dict which contains three variables: + - success, (bool) True if the payment is valid + - status, (str) The Paybox Response Code + - auth_code, (str) The Authorization Code generated by the Authorization Center + """ + + url_parsed = urlparse(response_url) + message = url_parsed.query + query = parse_qs(message) + + if verify_certificate: + self.verify_certificate(message=message, signature=query["SIGN"][0]) + + if not self.production: + assert query["AU"][0] == "XXXXXX", "Incorrect Test Authorization Code" + else: + assert "RC" in query, "No Response Code Returned" + + assert query["TO"][0] == str( + total + ), "Total does not match. PBX: %s - CMD: %s" % ( + query["TO"][0], + str(total), + ) + + return { + "success": True if query["RC"][0] == "00000" else False, + "status": self.RESPONSE_CODES.get( + query["RC"][0][:-2] + "xx", + self.RESPONSE_CODES.get(query["RC"][0], "Unrecognized Response Code"), + ), + "auth_code": query["AU"][0] if "AU" in query else False, + } + + def verify_certificate(self, message, signature): + """ Verifies the Paybox certificate, authenticity and alteration. + If everything goes well, returns True. Otherwise raise an Error + :message: (str), the full url with its args + :signature: (str), the signature of the message, separated from the url + Flow: + - The signature is decoded base64 + - The signature is removed from the message + - The Paybox pubkey is loaded from an external file + - it's validity is checked + - The message is digested by SHA1 + - The SHA1 message is verified against the binary signature + """ + + # detach the signature from the message + message_without_sign = message.split("&SIGN=")[0] + # decode base64 the signature + binary_signature = base64.b64decode(signature) + # create a pubkey object + if self.production: + key = RSA.importKey( + settings.PAYBOX_PUBLIC_KEY + ) + else: + key = RSA.importKey( + settings.PAYBOX_TEST_PUBLIC_KEY if settings.PAYBOX_TEST_PUBLIC_KEY else "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" + ) + # digest the message + h = SHA.new(bytes(message_without_sign, encoding="utf8")) + # and verify the signature + verifier = PKCS1_v1_5.new(key) + assert verifier.verify(h, binary_signature), "Signature Verification Failed" + + return True + diff --git a/example_paybox/test_payments/__init__.py b/example_paybox/test_payments/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/example_paybox/test_payments/settings.py b/example_paybox/test_payments/settings.py new file mode 100644 index 0000000..0e9a279 --- /dev/null +++ b/example_paybox/test_payments/settings.py @@ -0,0 +1,135 @@ +""" +Django settings for test_payments project. + +Generated by 'django-admin startproject' using Django 2.0.7. + +For more information on this file, see +https://docs.djangoproject.com/en/2.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/2.0/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'h_^d4@=64bc_-y&o2$3t=t-t$)yu-qcv^$v_dt!9-a*3j#1&mz' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'test_payments.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'test_payments.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/2.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/2.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/2.0/howto/static-files/ + +STATIC_URL = '/static/' + +# Django-paybox variables +# No 3D secure +PAYBOX_TEST_PUBLIC_KEY = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" +PAYBOX_PUBLIC_KEY = "" +PAYBOX_SITE = "1999888" +PAYBOX_RANG = "32" +PAYBOX_IDENTIFIANT = "107904482" + +# 3D Secure +# PAYBOX_TEST_PUBLIC_KEY = "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" +# PAYBOX_PUBLIC_KEY = "" +# PAYBOX_SITE = "1999888" +# PAYBOX_RANG = "32" +# PAYBOX_IDENTIFIANT = "107904482" diff --git a/example_paybox/test_payments/urls.py b/example_paybox/test_payments/urls.py new file mode 100644 index 0000000..fd8bed2 --- /dev/null +++ b/example_paybox/test_payments/urls.py @@ -0,0 +1,27 @@ +"""test_payments URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/2.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path +from .views import paybox_system, paybox_system_subscription, paybox_direct, paybox_direct_3d, paybox_direct_plus + +urlpatterns = [ + path('admin/', admin.site.urls), + path('paybox-system/', paybox_system), + path('paybox-system-subscription/', paybox_system_subscription), + path('paybox-direct/', paybox_direct), + path('paybox-direct-3d/', paybox_direct_3d), + path('paybox-direct-plus/', paybox_direct_plus) +] diff --git a/example_paybox/test_payments/views.py b/example_paybox/test_payments/views.py new file mode 100644 index 0000000..8c3b07f --- /dev/null +++ b/example_paybox/test_payments/views.py @@ -0,0 +1,102 @@ +from django.http import HttpResponse +import datetime +from paybox_test.paybox_system import PayboxSystemTransaction +from paybox_test.paybox_direct import PayboxDirectTransaction +from paybox_test.paybox_direct_plus import PayboxDirectPlusTransaction + + +def paybox_system(request): + transaction = PayboxSystemTransaction( + total="10000", + cmd="RANDOM", + porteur="arlusishmael@gmail.com", + time=datetime.datetime.now().isoformat() + ) + transaction.post_to_paybox() + form = transaction.construct_html_form() + return HttpResponse(form) + + +def paybox_system_subscription(request): + transaction = PayboxSystemTransaction( + total="200000", + cmd="RANDOM2", + porteur="wedavamagomere@gmail.com", + time=datetime.datetime.now().isoformat(), + subscription=True, + subscription_amount="200000", + nbpaie="0", + freq="1", + quand="0", + delais="2" + ) + transaction.post_to_paybox() + form = transaction.construct_html_form() + return HttpResponse(form) + + +def paybox_direct(request): + transaction = PayboxDirectTransaction( + operation_type="00001", + montant="40000", + reference="RANDOMynu", + cle="1999888I", + cvv="123", + porteur="1111222233334444", + dateval="1219", + secure_3d=False, + activite="024", + dateq=datetime.datetime.strftime(datetime.datetime.now(), '%d%m%Y'), + pays="" + ) + #authorize = transaction.post_to_paybox(numquestion="0000000005", operation_type="00001") + #sleep(1) + debit = transaction.post_to_paybox(numquestion="0000000006", operation_type="00002") + #sleep(1) + #refund = transaction.post_to_paybox(numquestion="0000000007", operation_type="00014") + return HttpResponse("") + + +def paybox_direct_3d(request): + transaction = PayboxDirectTransaction( + operation_type="00001", + montant="1000", + reference="yrjhvjgjixdrdxbutr", + cle="1999888I", + cvv="123", + porteur="4012001037141112", + dateval="1216", + secure_3d=True, + url_http_direct="https://dd883f2b.ngrok.io/callback1", + activite="024", + dateq=datetime.datetime.strftime(datetime.datetime.now(), '%d%m%Y'), + pays="", + devise="952" + ) + authorize_3d = transaction.remote_mpi_authenticate(session_id="buuhjfgdffcwtheg") + return authorize_3d + + +def paybox_direct_plus(request): + transaction = PayboxDirectPlusTransaction( + operation_type="00001", + montant="1000", + reference="yrjhvbngjixdrdtr", + cle="1999888I", + cvv="123", + porteur="1111222233334444", + dateval="1219", + url_http_direct="https://dd883f2b.ngrok.io/callback", + activite="027", + dateq=datetime.datetime.strftime(datetime.datetime.now(), '%d%m%Y'), + pays="", + devise="978", + refabonne="ITWORKhtbSMAN" + ) + #register = transaction.post_to_paybox(numquestion="0000000015", operation_type="00056") + #sleep(1) + #debit = transaction.post_to_paybox(numquestion="0000000016", operation_type="00052") + #sleep(1) + authorize_and_capture = transaction.post_to_paybox(numquestion="0000000017", operation_type="00053") + return HttpResponse() + diff --git a/example_paybox/test_payments/wsgi.py b/example_paybox/test_payments/wsgi.py new file mode 100644 index 0000000..072715c --- /dev/null +++ b/example_paybox/test_payments/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for test_payments project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_payments.settings") + +application = get_wsgi_application()