import requests import hashlib from urllib.parse import quote_plus, parse_qs import pprint pp = pprint.PrettyPrinter(indent=4) """ Exception thrown when hash from Paynow does not match locally generated hash """ class HashMismatchException(Exception): def __init__(self, message): super(HashMismatchException, self).__init__(message) # TODO: Verify hashes at all interactions with paynow ---> # TODO: Update status response class to support dictionary class StatusResponse: paid: bool """ bool: Boolean value indication whether the transaction was paid or not """ amount: float """ float: The total amount of the transaction """ reference: any """ any: The unique identifier for the transaction """ paynow_reference: any """ any: Paynow's unique identifier for the transaction """ def __status_update(self, data): """Parses the incoming status update from Paynow Args: data (any): The data from paynow """ print('Not implemented') # TODO: Implement method def __init__(self, data, update): if update: self.__status_update(data) else: self.paid = data['status'][0].lower() == 'paid' if 'amount' in data: self.amount = float(data['amount'][0]) if 'reference' in data: self.amount = data['reference'][0] class InitResponse: """Wrapper class for response from Paynow during transaction initiation """ success: bool """ bool: Boolean indicating whether initiate request was successful or not """ has_redirect: bool """ bool: Boolean indicating whether the response contains a url to redirect to """ hash: str """ str: Hashed transaction returned from Paynow """ redirect_url: str """ str: The url the user should be taken to so they can make a payment """ error: str """ str: he error message from Paynow, if any """ poll_url: str """ str: The poll URL sent from Paynow """ def __init__(self, data): self.success = data['status'][0].lower() != 'error' self.has_redirect = 'browserurl' in data self.hash = 'hash' in data print(data) if not self.success: self.error = data['error'][0] if self.has_redirect: self.redirect_url = data['browserurl'][0] self.poll_url = data['pollurl'][0] class Payment: """Helper class for building up a transaction before sending it off to Paynow Attributes: reference (str): Unique identifier for the transaction items ([]): Array of items in the 'cart' """ reference: str = "" """ str: Unique identifier for the transaction """ items: [] = [] """ []: Array of items in the 'cart' """ auth_email: str """ str: The user's email address. """ def __init__(self, reference: str, auth_email: str): self.reference = reference self.auth_email = auth_email def add(self, title: str, amount: float): """ Add an item to the 'cart' Args: title (str): The name of the item amount (float): The cost of the item """ # TODO: Validate self.items.append([title, amount]) return self def total(self): """Get the total cost of the items in the transaction Returns: float: The total """ total = 0.0 for item in self.items: total += float(item[1]) return total def info(self): """Generate text which represents the items in cart Returns: str: The text representation of the cart """ out = "" for item in self.items: print(item) out += (item[0] + ", ") return out class Paynow: """Contains helper methods to interact with the Paynow API Attributes: integration_id (str): Merchant's integration id. integration_key (str): Merchant's integration key. return_url (str): Merchant's return url result_url (str): Merchant's result url Args: integration_id (str): Merchant's integration id. (You can generate this in your merchant dashboard) integration_key (str): Merchant's integration key. return_url (str): Merchant's return url result_url (str): Merchant's result url """ URL_INITIATE_TRANSACTION = "https://paynow.webdevworld.com/interface/initiatetransaction" """ str: Transaction initation url (constant) """ URL_INITIATE_TRANSACTION = "https://paynow.webdevworld.com/interface/remotetransaction" """ str: Transaction initation url (constant) """ integration_id: str = "" """ str: Merchant's integration id """ integration_key: str = "" """ str: Merchant's integration key """ return_url = "" """ str: Merchant's return url """ result_url = "" """ str: Merchant's result url """ def __init__(self, integration_id, integration_key, return_url, result_url): self.integration_id = integration_id self.integration_key = integration_key self.return_url = return_url self.result_url = result_url def set_result_url(self, url: str): """Sets the url where the status of the transaction will be sent when payment status is updated within Paynow Args: url (str): The url where the status of the transaction will be sent when payment status is updated within Paynow """ self.result_url = url def set_return_url(self, url: str): """Sets the url where the user will be redirected to after they are done on Paynow Args: url (str): The url to redirect user to once they are done on Paynow's side """ self.return_url = url def create_payment(self, reference: str, auth_email: str = ''): """Create a new payment Args: reference (str): Unique identifier for the transaction. auth_email (str): The phone number to send to Paynow. This is required for mobile transactions Note: Auth email is required for mobile transactions. Returns: Payment: An object which provides an easy to use API to add items to Payment """ return Payment(reference, auth_email) def send(self, payment: Payment): """Send a transaction to Paynow Args: payment (Payment): The payment object with details about transaction Returns: StatusResponse: An object with information about the status of the transaction """ return self.__init(payment) def send_mobile(self, payment: Payment, phone: str, method: str): """Send a mobile transaction to Paynow Args: payment (Payment): The payment object with details about transaction phone (str): The phone number to send to Paynow method (str): The mobile money method being employed Returns: StatusResponse: An object with information about the status of the transaction """ return self.__init_mobile(payment, phone, method) def process_status_update(self, data): """This method parses the status update data from Paynow into an easier to use format Args: data (dict): A dictionary with the data from Paynow. This is the POST data sent by Paynow to your result url after the status of a transaction has changed (see Django usage example) Returns: StatusResponse: An object with information about the status of the transaction """ return StatusResponse(data, True) def __init(self, payment: Payment): """Initiate the given transaction with Paynow Args: payment (Payment): The payment object with details about transaction Returns: InitResponse: An object with misc information about the initiated transaction i.e redirect url (if available), status of initiation etc (see `InitResponse` declaration above) """ if payment.total() <= 0: raise ValueError('Transaction total cannot be less than 1') data = self.__build(payment) print("Normal init request object: ", pp.pformat(data)) response = requests.post(self.URL_INITIATE_TRANSACTION, data=data) response_object = parse_qs(response.text) print("Normal init response object: ", pp.pformat(response_object)) if(str(response_object['status'][0]).lower() == 'error'): return InitResponse(response_object) if not self.__verify_hash(response_object, self.integration_key): raise HashMismatchException("Hashes do not match") return InitResponse(response_object) def __init_mobile(self, payment: Payment, phone: str, method: str): """Initiate a mobile transaction Args: payment (Payment): The payment object with details about transaction phone (str): The phone number to send to Paynow method (str): The mobile money method being employed Returns: InitResponse: An object with misc information about the initiated transaction i.e redirect url (if available), status of initiation etc (see `InitResponse` declaration above) """ if payment.total() <= 0: raise ValueError('Transaction total cannot be less than 1') data = self.__build_mobile(payment, phone, method) response = requests.post(self.URL_INITIATE_TRANSACTION, data=data) response_object = parse_qs(response.text) print("Mobile init response object: ", pp.pformat(response_object)) return InitResponse(response_object) def check_transaction_status(self, poll_url): """Check the status transaction of the transaction with the given poll url Args: poll_url (str): Poll url of the transaction Returns: StatusResponse: An object with information about the status of the transaction """ response = requests.post(self.URL_INITIATE_TRANSACTION, data={}) return StatusResponse( parse_qs(response.text ), False) def __build(self, payment: Payment): """Build up a payment into the format required by Paynow Args: payment (Payment): The payment object to format Returns: dict: A dictionary properly formatted in the format required by Paynow """ body = { "resulturl": self.result_url, "returnurl": self.return_url, "reference": payment.reference, "amount": payment.total(), "id": self.integration_id, "additionalinfo": payment.info(), "authemail": "", "status": "Message" } body['hash'] = self.__hash(body, self.integration_key) for key, value in body.items(): body[key] = quote_plus(str(value)) return body def __build_mobile(self, payment: Payment, phone: str, method: str): """Build up a mobile payment into the format required by Paynow Args: payment (Payment): The payment object to format phone (str): The phone number to send to Paynow method (str): The mobile money method being employed Note: Currently supported methods are `ecocash` and `onemoney` Returns: dict: A dictionary properly formatted in the format required by Paynow """ body = { "resulturl": self.result_url, "returnurl": self.return_url, "reference": payment.reference, "amount": payment.total(), "id": self.integration_id, "additionalinfo": payment.info(), "authemail": payment.auth_email, "phone": phone, "method": method, "status": "Message" } for key, value in body.items(): body[key] = quote_plus(str(value)) # Url encode the body['hash'] = self.__hash(body, self.integration_key) return body def __hash(self, items: {}, integration_key: str): """Generates a SHA512 hash of the transaction Args: items (dict): The transaction dictionary to hash integration_key (str): Merchant integration key to use during hashing Returns: str: The hashed transaction """ out = "" for key, value in items.items(): out += str(value) out += integration_key return hashlib.sha512(out.encode('utf-8')).hexdigest().upper() def __verify_hash(self, response: {}, integration_key: str): """Verify the hash coming from Paynow Args: response (dict): The response from Paynow integration_key (str): Merchant integration key to use during hashing """ pp.pprint(response) if('hash' not in response): raise ValueError("Response from Paynow does not contain a hash") hash = response['hash'] return hash == self.__hash(response, integration_key)
Run
Reset
Share
Import
Link
Embed
Language▼
English
中文
Python Fiddle
Python Cloud IDE
Follow @python_fiddle
Browser Version Not Supported
Due to Python Fiddle's reliance on advanced JavaScript techniques, older browsers might have problems running it correctly. Please download the latest version of your favourite browser.
Chrome 10+
Firefox 4+
Safari 5+
IE 10+
Let me try anyway!
url:
Go
Python Snippet
Stackoverflow Question