Skip to content

Commit

Permalink
Merge pull request #11 from navariltd/webshop
Browse files Browse the repository at this point in the history
feat - webshop mpesa express
  • Loading branch information
maniamartial authored Dec 30, 2024
2 parents 12be180 + 1cc0501 commit abfaf24
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 81 deletions.
84 changes: 84 additions & 0 deletions frappe_mpsa_payments/frappe_mpsa_payments/api/m_pesa_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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 ""
),
},
)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -167,7 +172,8 @@ 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"
# "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:])
Expand All @@ -184,9 +190,9 @@ 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(args.phone_number)
# mobile_number = sanitize_mobile_number(args.sender)
response = connector.stk_push(
business_shortcode=business_shortcode,
amount=args.request_amount,
Expand Down Expand Up @@ -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]:
Expand Down
14 changes: 14 additions & 0 deletions frappe_mpsa_payments/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))

0 comments on commit abfaf24

Please sign in to comment.