fix(mpesa-settings): add test cases to verify transactions
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
index 1d318bb..dea4d81 100644
--- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
@@ -26,11 +26,19 @@
def on_update(self):
create_custom_pos_fields()
create_payment_gateway('Mpesa-' + self.payment_gateway_name, settings='Mpesa Settings', controller=self.payment_gateway_name)
- create_mode_of_payment('Mpesa-' + self.payment_gateway_name, payment_type="Phone")
call_hook_method('payment_gateway_enabled', gateway='Mpesa-' + self.payment_gateway_name, payment_channel="Phone")
+ # required to fetch the bank account details from the payment gateway account
+ frappe.db.commit()
+ create_mode_of_payment('Mpesa-' + self.payment_gateway_name, payment_type="Phone")
+
def request_for_payment(self, **kwargs):
- response = frappe._dict(generate_stk_push(**kwargs))
+ if frappe.flags.in_test:
+ from erpnext.erpnext_integrations.doctype.mpesa_settings.test_mpesa_settings import get_payment_request_response_payload
+ response = frappe._dict(get_payment_request_response_payload())
+ else:
+ response = frappe._dict(generate_stk_push(**kwargs))
+
self.handle_api_response("CheckoutRequestID", kwargs, response)
def get_account_balance_info(self):
@@ -39,7 +47,13 @@
reference_docname=self.name,
doc_details=vars(self)
)
- response = frappe._dict(get_account_balance(payload))
+
+ if frappe.flags.in_test:
+ from erpnext.erpnext_integrations.doctype.mpesa_settings.test_mpesa_settings import get_test_account_balance_response
+ response = frappe._dict(get_test_account_balance_response())
+ else:
+ response = frappe._dict(get_account_balance(payload))
+
self.handle_api_response("ConversationID", payload, response)
def handle_api_response(self, global_id, request_dict, response):
@@ -92,7 +106,6 @@
def verify_transaction(**kwargs):
"""Verify the transaction result received via callback from stk."""
transaction_response = frappe._dict(kwargs["Body"]["stkCallback"])
- frappe.logger().debug(transaction_response)
checkout_id = getattr(transaction_response, "CheckoutRequestID", "")
request = frappe.get_doc("Integration Request", checkout_id)
@@ -148,14 +161,13 @@
return
transaction_data = frappe._dict(loads(request.data))
- frappe.logger().debug(account_balance_response)
if account_balance_response["ResultCode"] == 0:
try:
result_params = account_balance_response["ResultParameters"]["ResultParameter"]
balance_info = fetch_param_value(result_params, "AccountBalance", "Key")
- balance_info = convert_to_json(balance_info)
+ balance_info = format_string_to_json(balance_info)
ref_doc = frappe.get_doc(transaction_data.reference_doctype, transaction_data.reference_docname)
ref_doc.db_set("account_balance", balance_info)
@@ -168,15 +180,15 @@
else:
request.handle_failure(account_balance_response)
-def convert_to_json(balance_info):
+def format_string_to_json(balance_info):
"""
- Convert string to json.
+ Format string to json.
e.g: '''Working Account|KES|481000.00|481000.00|0.00|0.00'''
=> {'Working Account': {'current_balance': '481000.00',
'available_balance': '481000.00',
'reserved_balance': '0.00',
- 'uncleared_balance': '0.00'}
+ 'uncleared_balance': '0.00'}}
"""
balance_dict = frappe._dict()
for account_info in balance_info.split("&"):
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
index 4aa970e..55ccff3 100644
--- a/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/test_mpesa_settings.py
@@ -2,9 +2,239 @@
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
-
-# import frappe
+from json import dumps
+import frappe
import unittest
+from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_settings import process_balance_info, verify_transaction
+from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
class TestMpesaSettings(unittest.TestCase):
- pass
+ def test_creation_of_payment_gateway(self):
+ mpesa_doc = create_mpesa_settings(payment_gateway_name="_Test")
+
+ mode_of_payment = frappe.get_doc("Mode of Payment", "Mpesa-_Test")
+ self.assertTrue(frappe.db.exists("Payment Gateway Account", {'payment_gateway': "Mpesa-_Test"}))
+ self.assertTrue(mode_of_payment.name)
+ self.assertEquals(mode_of_payment.type, "Phone")
+
+ def test_processing_of_account_balance(self):
+ mpesa_doc = create_mpesa_settings(payment_gateway_name="_Account Balance")
+ mpesa_doc.get_account_balance_info()
+
+ callback_response = get_account_balance_callback_payload()
+ process_balance_info(**callback_response)
+ integration_request = frappe.get_doc("Integration Request", "AG_20200927_00007cdb1f9fb6494315")
+
+ # test integration request creation and successful update of the status on receiving callback response
+ self.assertTrue(integration_request)
+ self.assertEquals(integration_request.status, "Completed")
+
+ # test formatting of account balance received as string to json with appropriate currency symbol
+ mpesa_doc.reload()
+ self.assertEquals(mpesa_doc.account_balance, dumps({
+ "Working Account": {
+ "current_balance": "Sh 481,000.00",
+ "available_balance": "Sh 481,000.00",
+ "reserved_balance": "Sh 0.00",
+ "uncleared_balance": "Sh 0.00"
+ }
+ }))
+
+ def test_processing_of_callback_payload(self):
+ mpesa_doc = create_mpesa_settings(payment_gateway_name="Payment")
+ mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account")
+ frappe.db.set_value("Account", mpesa_account, "account_currency", "KES")
+
+ pos_invoice = create_pos_invoice(do_not_submit=1)
+ pos_invoice.append("payments", {'mode_of_payment': 'Mpesa-Payment', 'account': mpesa_account, 'amount': 500})
+ pos_invoice.contact_mobile = "093456543894"
+ pos_invoice.currency = "KES"
+ pos_invoice.save()
+
+ pr = pos_invoice.create_payment_request()
+ # test payment request creation
+ self.assertEquals(pr.payment_gateway, "Mpesa-Payment")
+
+ callback_response = get_payment_callback_payload()
+ verify_transaction(**callback_response)
+ # test creation of integration request
+ integration_request = frappe.get_doc("Integration Request", "ws_CO_061020201133231972")
+
+ # test integration request creation and successful update of the status on receiving callback response
+ self.assertTrue(integration_request)
+ self.assertEquals(integration_request.status, "Completed")
+
+ pos_invoice.reload()
+ integration_request.reload()
+ self.assertEquals(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R")
+ self.assertEquals(integration_request.status, "Completed")
+
+def create_mpesa_settings(payment_gateway_name="Express"):
+ if frappe.db.exists("Mpesa Settings", payment_gateway_name):
+ return frappe.get_doc("Mpesa Settings", payment_gateway_name)
+
+ doc = frappe.get_doc(dict(
+ doctype="Mpesa Settings",
+ payment_gateway_name=payment_gateway_name,
+ consumer_key="5sMu9LVI1oS3oBGPJfh3JyvLHwZOdTKn",
+ consumer_secret="VI1oS3oBGPJfh3JyvLHw",
+ online_passkey="LVI1oS3oBGPJfh3JyvLHwZOd",
+ till_number="174379"
+ ))
+
+ doc.insert(ignore_permissions=True)
+ return doc
+
+def get_test_account_balance_response():
+ """Response received after calling the account balance API."""
+ return {
+ "ResultType":0,
+ "ResultCode":0,
+ "ResultDesc":"The service request has been accepted successfully.",
+ "OriginatorConversationID":"10816-694520-2",
+ "ConversationID":"AG_20200927_00007cdb1f9fb6494315",
+ "TransactionID":"LGR0000000",
+ "ResultParameters":{
+ "ResultParameter":[
+ {
+ "Key":"ReceiptNo",
+ "Value":"LGR919G2AV"
+ },
+ {
+ "Key":"Conversation ID",
+ "Value":"AG_20170727_00004492b1b6d0078fbe"
+ },
+ {
+ "Key":"FinalisedTime",
+ "Value":20170727101415
+ },
+ {
+ "Key":"Amount",
+ "Value":10
+ },
+ {
+ "Key":"TransactionStatus",
+ "Value":"Completed"
+ },
+ {
+ "Key":"ReasonType",
+ "Value":"Salary Payment via API"
+ },
+ {
+ "Key":"TransactionReason"
+ },
+ {
+ "Key":"DebitPartyCharges",
+ "Value":"Fee For B2C Payment|KES|33.00"
+ },
+ {
+ "Key":"DebitAccountType",
+ "Value":"Utility Account"
+ },
+ {
+ "Key":"InitiatedTime",
+ "Value":20170727101415
+ },
+ {
+ "Key":"Originator Conversation ID",
+ "Value":"19455-773836-1"
+ },
+ {
+ "Key":"CreditPartyName",
+ "Value":"254708374149 - John Doe"
+ },
+ {
+ "Key":"DebitPartyName",
+ "Value":"600134 - Safaricom157"
+ }
+ ]
+ },
+ "ReferenceData":{
+ "ReferenceItem":{
+ "Key":"Occasion",
+ "Value":"aaaa"
+ }
+ }
+ }
+
+def get_payment_request_response_payload():
+ """Response received after successfully calling the stk push process request API."""
+ return {
+ "MerchantRequestID": "8071-27184008-1",
+ "CheckoutRequestID": "ws_CO_061020201133231972",
+ "ResultCode": 0,
+ "ResultDesc": "The service request is processed successfully.",
+ "CallbackMetadata": {
+ "Item": [
+ { "Name": "Amount", "Value": 500.0 },
+ { "Name": "MpesaReceiptNumber", "Value": "LGR7OWQX0R" },
+ { "Name": "TransactionDate", "Value": 20201006113336 },
+ { "Name": "PhoneNumber", "Value": 254723575670 }
+ ]
+ }
+ }
+
+
+def get_payment_callback_payload():
+ """Response received from the server as callback after calling the stkpush process request API."""
+ return {
+ "Body":{
+ "stkCallback":{
+ "MerchantRequestID":"19465-780693-1",
+ "CheckoutRequestID":"ws_CO_061020201133231972",
+ "ResultCode":0,
+ "ResultDesc":"The service request is processed successfully.",
+ "CallbackMetadata":{
+ "Item":[
+ {
+ "Name":"Amount",
+ "Value":500
+ },
+ {
+ "Name":"MpesaReceiptNumber",
+ "Value":"LGR7OWQX0R"
+ },
+ {
+ "Name":"Balance"
+ },
+ {
+ "Name":"TransactionDate",
+ "Value":20170727154800
+ },
+ {
+ "Name":"PhoneNumber",
+ "Value":254721566839
+ }
+ ]
+ }
+ }
+ }
+ }
+
+def get_account_balance_callback_payload():
+ """Response received from the server as callback after calling the account balance API."""
+ return {
+ "Result":{
+ "ResultType": 0,
+ "ResultCode": 0,
+ "ResultDesc": "The service request is processed successfully.",
+ "OriginatorConversationID": "16470-170099139-1",
+ "ConversationID": "AG_20200927_00007cdb1f9fb6494315",
+ "TransactionID": "OIR0000000",
+ "ResultParameters": {
+ "ResultParameter": [
+ {
+ "Key": "AccountBalance",
+ "Value": "Working Account|KES|481000.00|481000.00|0.00|0.00"
+ },
+ { "Key": "BOCompletedTime", "Value": 20200927234123 }
+ ]
+ },
+ "ReferenceData": {
+ "ReferenceItem": {
+ "Key": "QueueTimeoutURL",
+ "Value": "https://internalsandbox.safaricom.co.ke/mpesa/abresults/v1/submit"
+ }
+ }
+ }
+ }
\ No newline at end of file