From 08f04611302c5228644c75cfd3a2f8f5792efe1f Mon Sep 17 00:00:00 2001 From: maniamartial Date: Thu, 26 Dec 2024 19:51:53 +0300 Subject: [PATCH 1/2] feat - wenbshop mpesa express --- .../frappe_mpsa_payments/api/m_pesa_api.py | 84 ++++++++++++++++ .../doctype/mpesa_settings/mpesa_settings.py | 96 +++---------------- frappe_mpsa_payments/utils/utils.py | 14 +++ 3 files changed, 111 insertions(+), 83 deletions(-) diff --git a/frappe_mpsa_payments/frappe_mpsa_payments/api/m_pesa_api.py b/frappe_mpsa_payments/frappe_mpsa_payments/api/m_pesa_api.py index 1724ef1..15d5be6 100644 --- a/frappe_mpsa_payments/frappe_mpsa_payments/api/m_pesa_api.py +++ b/frappe_mpsa_payments/frappe_mpsa_payments/api/m_pesa_api.py @@ -4,6 +4,13 @@ from frappe import _ from requests.auth import HTTPBasicAuth import json +from ..doctype.mpesa_settings.mpesa_settings import ( + get_completed_integration_requests_info, + fetch_param_value, + +) + +from json import dumps, loads def get_token(app_key, app_secret, base_url): @@ -189,3 +196,80 @@ def get_mode_of_payment(mpesa_doc): mode_of_payment = frappe.get_value("Mpesa C2B Payment Register URL", {"till_number": business_short_code, "register_status": "Success"}, "mode_of_payment") return mode_of_payment + +@frappe.whitelist(allow_guest=True) +def verify_transaction(**kwargs) -> None: + """Verify the transaction result received via callback from stk.""" + + transaction_response = frappe._dict(kwargs["Body"]["stkCallback"]) + + checkout_id = getattr(transaction_response, "CheckoutRequestID", "") + if not isinstance(checkout_id, str): + frappe.throw(_("Invalid Checkout Request ID")) + print("=====================================") + print(str(transaction_response)) + integration_request = frappe.get_doc("Integration Request", checkout_id) + transaction_data = frappe._dict(loads(integration_request.data)) + total_paid = 0 + success = False # for reporting successfull callback to point of sale ui + + if transaction_response["ResultCode"] == 0: + if ( + integration_request.reference_doctype + and integration_request.reference_docname + ): + try: + item_response = transaction_response["CallbackMetadata"]["Item"] + amount = fetch_param_value(item_response, "Amount", "Name") + mpesa_receipt = fetch_param_value( + item_response, "MpesaReceiptNumber", "Name" + ) + pr = frappe.get_doc( + integration_request.reference_doctype, + integration_request.reference_docname, + ) + + mpesa_receipts, completed_payments = ( + get_completed_integration_requests_info( + integration_request.reference_doctype, + integration_request.reference_docname, + checkout_id, + ) + ) + + total_paid = amount + sum(completed_payments) + mpesa_receipts = ", ".join(mpesa_receipts + [mpesa_receipt]) + + if total_paid >= pr.grand_total: + pr.run_method("on_payment_authorized", "Completed") + success = True + + frappe.db.set_value( + "POS Invoice", + pr.reference_name, + "mpesa_receipt_number", + mpesa_receipts, + ) + integration_request.handle_success(transaction_response) + except Exception: + integration_request.handle_failure(transaction_response) + frappe.log_error("Mpesa: Failed to verify transaction") + + else: + integration_request.handle_failure(transaction_response) + + frappe.publish_realtime( + event="process_phone_payment", + doctype="POS Invoice", + docname=transaction_data.payment_reference, + user=integration_request.owner, + message={ + "amount": total_paid, + "success": success, + "failure_message": ( + transaction_response["ResultDesc"] + if transaction_response["ResultCode"] != 0 + else "" + ), + }, + ) \ No newline at end of file diff --git a/frappe_mpsa_payments/frappe_mpsa_payments/doctype/mpesa_settings/mpesa_settings.py b/frappe_mpsa_payments/frappe_mpsa_payments/doctype/mpesa_settings/mpesa_settings.py index edea7d2..7c99442 100644 --- a/frappe_mpsa_payments/frappe_mpsa_payments/doctype/mpesa_settings/mpesa_settings.py +++ b/frappe_mpsa_payments/frappe_mpsa_payments/doctype/mpesa_settings/mpesa_settings.py @@ -62,6 +62,11 @@ def before_insert(self) -> None: self.security_credential = base64.b64encode(ciphertext).decode("utf-8") + @frappe.whitelist() + def get_payment_url(self, **kwargs) -> str: + """Return the payment URL""" + return "/all-products" + def on_update(self) -> None: """On Update Hook""" from ....utils.utils import create_payment_gateway @@ -145,7 +150,7 @@ def handle_api_response( ) -> None: """Response received from API calls returns a global identifier for each transaction, this code is returned during the callback.""" # check error response - if response["requestId"]: + if "requestId" in response: req_name = response["requestId"] error = response else: @@ -166,8 +171,9 @@ def generate_stk_push(**kwargs) -> str | Any: try: callback_url = ( - get_request_site_address(True) - + "/api/method/payments.payment_gateways.doctype.mpesa_settings.mpesa_settings.verify_transaction" + # get_request_site_address(True) + "https://9836-41-80-117-181.ngrok-free.app" + + "/api/method/frappe_mpsa_payments.frappe_mpsa_payments.api.m_pesa_api.verify_transaction" ) mpesa_settings = frappe.get_doc("Mpesa Settings", args.payment_gateway[6:]) @@ -184,12 +190,12 @@ def generate_stk_push(**kwargs) -> str | Any: app_key=mpesa_settings.consumer_key, app_secret=mpesa_settings.get_password("consumer_secret"), ) - - mobile_number = sanitize_mobile_number(args.sender) - + phone_no='0740743521' + mobile_number=sanitize_mobile_number(phone_no) + # mobile_number = sanitize_mobile_number(args.sender) response = connector.stk_push( business_shortcode=business_shortcode, - amount=args.request_amount, + amount=1, passcode=mpesa_settings.get_password("online_passkey"), callback_url=callback_url, reference_code=mpesa_settings.till_number, @@ -214,82 +220,6 @@ def sanitize_mobile_number(number: str) -> str: return "254" + str(number).lstrip("0") -@frappe.whitelist(allow_guest=True) -def verify_transaction(**kwargs) -> None: - """Verify the transaction result received via callback from stk.""" - transaction_response = frappe._dict(kwargs["Body"]["stkCallback"]) - - checkout_id = getattr(transaction_response, "CheckoutRequestID", "") - if not isinstance(checkout_id, str): - frappe.throw(_("Invalid Checkout Request ID")) - - integration_request = frappe.get_doc("Integration Request", checkout_id) - transaction_data = frappe._dict(loads(integration_request.data)) - total_paid = 0 # for multiple integration request made against a pos invoice - success = False # for reporting successfull callback to point of sale ui - - if transaction_response["ResultCode"] == 0: - if ( - integration_request.reference_doctype - and integration_request.reference_docname - ): - try: - item_response = transaction_response["CallbackMetadata"]["Item"] - amount = fetch_param_value(item_response, "Amount", "Name") - mpesa_receipt = fetch_param_value( - item_response, "MpesaReceiptNumber", "Name" - ) - pr = frappe.get_doc( - integration_request.reference_doctype, - integration_request.reference_docname, - ) - - mpesa_receipts, completed_payments = ( - get_completed_integration_requests_info( - integration_request.reference_doctype, - integration_request.reference_docname, - checkout_id, - ) - ) - - total_paid = amount + sum(completed_payments) - mpesa_receipts = ", ".join(mpesa_receipts + [mpesa_receipt]) - - if total_paid >= pr.grand_total: - pr.run_method("on_payment_authorized", "Completed") - success = True - - frappe.db.set_value( - "POS Invoice", - pr.reference_name, - "mpesa_receipt_number", - mpesa_receipts, - ) - integration_request.handle_success(transaction_response) - except Exception: - integration_request.handle_failure(transaction_response) - frappe.log_error("Mpesa: Failed to verify transaction") - - else: - integration_request.handle_failure(transaction_response) - - frappe.publish_realtime( - event="process_phone_payment", - doctype="POS Invoice", - docname=transaction_data.payment_reference, - user=integration_request.owner, - message={ - "amount": total_paid, - "success": success, - "failure_message": ( - transaction_response["ResultDesc"] - if transaction_response["ResultCode"] != 0 - else "" - ), - }, - ) - - def get_completed_integration_requests_info( reference_doctype: str, reference_docname: str, checkout_id: str ) -> tuple[list, list]: diff --git a/frappe_mpsa_payments/utils/utils.py b/frappe_mpsa_payments/utils/utils.py index f6a51cf..4372f8f 100644 --- a/frappe_mpsa_payments/utils/utils.py +++ b/frappe_mpsa_payments/utils/utils.py @@ -64,3 +64,17 @@ def save_access_token( # TODO: Not sure what exception is thrown here. Confirm frappe.throw("Error Encountered") return False + +def get_payment_gateway_controller(payment_gateway): + """Return payment gateway controller""" + gateway = frappe.get_doc("Payment Gateway", payment_gateway) + if gateway.gateway_controller is None: + try: + return frappe.get_doc(f"{payment_gateway} Settings") + except Exception: + frappe.throw(_("{0} Settings not found").format(payment_gateway)) + else: + try: + return frappe.get_doc(gateway.gateway_settings, gateway.gateway_controller) + except Exception: + frappe.throw(_("{0} Settings not found").format(payment_gateway)) From 1cc0501adfbfe4b4f2c6840f485da85d2134e6a0 Mon Sep 17 00:00:00 2001 From: maniamartial Date: Mon, 30 Dec 2024 15:49:34 +0300 Subject: [PATCH 2/2] feat - direction to get site address --- .../doctype/mpesa_settings/mpesa_settings.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe_mpsa_payments/frappe_mpsa_payments/doctype/mpesa_settings/mpesa_settings.py b/frappe_mpsa_payments/frappe_mpsa_payments/doctype/mpesa_settings/mpesa_settings.py index 7c99442..f2253fb 100644 --- a/frappe_mpsa_payments/frappe_mpsa_payments/doctype/mpesa_settings/mpesa_settings.py +++ b/frappe_mpsa_payments/frappe_mpsa_payments/doctype/mpesa_settings/mpesa_settings.py @@ -171,8 +171,8 @@ def generate_stk_push(**kwargs) -> str | Any: try: callback_url = ( - # get_request_site_address(True) - "https://9836-41-80-117-181.ngrok-free.app" + get_request_site_address(True) + # "https://9836-41-80-117-181.ngrok-free.app" + "/api/method/frappe_mpsa_payments.frappe_mpsa_payments.api.m_pesa_api.verify_transaction" ) @@ -190,12 +190,12 @@ def generate_stk_push(**kwargs) -> str | Any: app_key=mpesa_settings.consumer_key, app_secret=mpesa_settings.get_password("consumer_secret"), ) - phone_no='0740743521' - mobile_number=sanitize_mobile_number(phone_no) + # phone_no='0740743521' + mobile_number=sanitize_mobile_number(args.phone_number) # mobile_number = sanitize_mobile_number(args.sender) response = connector.stk_push( business_shortcode=business_shortcode, - amount=1, + amount=args.request_amount, passcode=mpesa_settings.get_password("online_passkey"), callback_url=callback_url, reference_code=mpesa_settings.till_number,