mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-24 23:49:19 +00:00
fix: fetch account balance info
This commit is contained in:
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
{% if not jQuery.isEmptyObject(data) %}
|
||||||
|
<h5 style="margin-top: 20px;"> {{ __("Balance Details") }} </h5>
|
||||||
|
<table class="table table-bordered small">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 20%">{{ __("Account Type") }}</th>
|
||||||
|
<th style="width: 20%" class="text-right">{{ __("Current Balance") }}</th>
|
||||||
|
<th style="width: 20%" class="text-right">{{ __("Available Balance") }}</th>
|
||||||
|
<th style="width: 20%" class="text-right">{{ __("Reserved Balance") }}</th>
|
||||||
|
<th style="width: 20%" class="text-right">{{ __("Uncleared Balance") }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for(const [key, value] of Object.entries(data)) { %}
|
||||||
|
<tr>
|
||||||
|
<td> {%= key %} </td>
|
||||||
|
<td class="text-right"> {%= value["current_balance"] %} </td>
|
||||||
|
<td class="text-right"> {%= value["available_balance"] %} </td>
|
||||||
|
<td class="text-right"> {%= value["reserved_balance"] %} </td>
|
||||||
|
<td class="text-right"> {%= value["uncleared_balance"] %} </td>
|
||||||
|
</tr>
|
||||||
|
{% } %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p style="margin-top: 30px;"> Account Balance Information Not Available. </p>
|
||||||
|
{% endif %}
|
||||||
@@ -2,6 +2,27 @@
|
|||||||
// For license information, please see license.txt
|
// For license information, please see license.txt
|
||||||
|
|
||||||
frappe.ui.form.on('Mpesa Settings', {
|
frappe.ui.form.on('Mpesa Settings', {
|
||||||
// refresh: function(frm) {
|
onload_post_render: function(frm) {
|
||||||
// }
|
frm.events.setup_account_balance_html(frm);
|
||||||
|
},
|
||||||
|
|
||||||
|
get_account_balance: function(frm) {
|
||||||
|
if (!frm.initiator_name && !frm.security_credentials) return;
|
||||||
|
frappe.call({
|
||||||
|
method: "get_account_balance_info",
|
||||||
|
doc: frm.doc
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setup_account_balance_html: function(frm) {
|
||||||
|
console.log(frm.doc.account_balance)
|
||||||
|
$("div").remove(".form-dashboard-section.custom");
|
||||||
|
frm.dashboard.add_section(
|
||||||
|
frappe.render_template('account_balance', {
|
||||||
|
data: JSON.parse(frm.doc.account_balance)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
frm.dashboard.show();
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,10 +9,14 @@
|
|||||||
"payment_gateway_name",
|
"payment_gateway_name",
|
||||||
"consumer_key",
|
"consumer_key",
|
||||||
"consumer_secret",
|
"consumer_secret",
|
||||||
"column_break_4",
|
"initiator_name",
|
||||||
"till_number",
|
"till_number",
|
||||||
|
"sandbox",
|
||||||
|
"column_break_4",
|
||||||
"online_passkey",
|
"online_passkey",
|
||||||
"sandbox"
|
"security_credential",
|
||||||
|
"get_account_balance",
|
||||||
|
"account_balance"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -58,11 +62,32 @@
|
|||||||
"fieldtype": "Password",
|
"fieldtype": "Password",
|
||||||
"label": " Online PassKey",
|
"label": " Online PassKey",
|
||||||
"reqd": 1
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "initiator_name",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Initiator Name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "security_credential",
|
||||||
|
"fieldtype": "Small Text",
|
||||||
|
"label": "Security Credential"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "account_balance",
|
||||||
|
"fieldtype": "Long Text",
|
||||||
|
"hidden": 1,
|
||||||
|
"label": "Account Balance",
|
||||||
|
"read_only": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "get_account_balance",
|
||||||
|
"fieldtype": "Button",
|
||||||
|
"label": "Get Account Balance"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-09-10 09:07:28.557461",
|
"modified": "2020-09-25 20:21:38.215494",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "ERPNext Integrations",
|
"module": "ERPNext Integrations",
|
||||||
"name": "Mpesa Settings",
|
"name": "Mpesa Settings",
|
||||||
|
|||||||
@@ -4,17 +4,14 @@
|
|||||||
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import json
|
from json import loads, dumps
|
||||||
import requests
|
|
||||||
from six.moves.urllib.parse import urlencode
|
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import get_url, call_hook_method, cint, flt, cstr
|
from frappe.utils import call_hook_method
|
||||||
from frappe.integrations.utils import create_request_log, create_payment_gateway
|
from frappe.integrations.utils import create_request_log, create_payment_gateway
|
||||||
from frappe.utils import get_request_site_address
|
from frappe.utils import get_request_site_address
|
||||||
from frappe.utils.password import get_decrypted_password
|
|
||||||
from frappe.utils import get_request_site_address
|
from frappe.utils import get_request_site_address
|
||||||
from erpnext.erpnext_integrations.utils import create_mode_of_payment
|
from erpnext.erpnext_integrations.utils import create_mode_of_payment
|
||||||
from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_connector import MpesaConnector
|
from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_connector import MpesaConnector
|
||||||
@@ -35,16 +32,29 @@ class MpesaSettings(Document):
|
|||||||
|
|
||||||
def request_for_payment(self, **kwargs):
|
def request_for_payment(self, **kwargs):
|
||||||
response = frappe._dict(generate_stk_push(**kwargs))
|
response = frappe._dict(generate_stk_push(**kwargs))
|
||||||
|
self.handle_api_response("CheckoutRequestID", kwargs, response)
|
||||||
|
|
||||||
|
def get_account_balance_info(self):
|
||||||
|
payload = dict(
|
||||||
|
reference_doctype="Mpesa Settings",
|
||||||
|
reference_docname=self.name,
|
||||||
|
doc_details=vars(self)
|
||||||
|
)
|
||||||
|
response = frappe._dict(get_account_balance(payload))
|
||||||
|
self.handle_api_response("ConversationID", payload, response)
|
||||||
|
|
||||||
|
def handle_api_response(self, global_id, request_dict, response):
|
||||||
# check error response
|
# check error response
|
||||||
if hasattr(response, "requestId"):
|
if getattr(response, "requestId"):
|
||||||
req_name = getattr(response, "requestId")
|
req_name = getattr(response, "requestId")
|
||||||
error = response
|
error = response
|
||||||
else:
|
else:
|
||||||
# global checkout id used as request name
|
# global checkout id used as request name
|
||||||
req_name = getattr(response, "CheckoutRequestID")
|
req_name = getattr(response, global_id)
|
||||||
error = None
|
error = None
|
||||||
|
|
||||||
create_request_log(kwargs, "Host", "Mpesa", req_name, error)
|
create_request_log(request_dict, "Host", "Mpesa", req_name, error)
|
||||||
|
|
||||||
if error:
|
if error:
|
||||||
frappe.throw(_(getattr(response, "errorMessage")), title=_("Transaction Error"))
|
frappe.throw(_(getattr(response, "errorMessage")), title=_("Transaction Error"))
|
||||||
|
|
||||||
@@ -76,24 +86,84 @@ def verify_transaction(**kwargs):
|
|||||||
""" Verify the transaction result received via callback """
|
""" Verify the transaction result received via callback """
|
||||||
transaction_response = frappe._dict(kwargs["Body"]["stkCallback"])
|
transaction_response = frappe._dict(kwargs["Body"]["stkCallback"])
|
||||||
|
|
||||||
checkout_id = getattr(transaction_response, "CheckoutRequestID")
|
checkout_id = getattr(transaction_response, "CheckoutRequestID", "")
|
||||||
request = frappe.get_doc("Integration Request", checkout_id)
|
request = frappe.get_doc("Integration Request", checkout_id)
|
||||||
transaction_data = frappe._dict(json.loads(request.data))
|
transaction_data = frappe._dict(loads(request.data))
|
||||||
|
|
||||||
if transaction_response['ResultCode'] == 0:
|
if transaction_response['ResultCode'] == 0:
|
||||||
if transaction_data.reference_doctype and transaction_data.reference_docname:
|
if transaction_data.reference_doctype and transaction_data.reference_docname:
|
||||||
try:
|
try:
|
||||||
frappe.get_doc(transaction_data.reference_doctype,
|
frappe.get_doc(transaction_data.reference_doctype,
|
||||||
transaction_data.reference_docname).run_method("on_payment_authorized", 'Completed')
|
transaction_data.reference_docname).run_method("on_payment_authorized", 'Completed')
|
||||||
request.db_set('output', transaction_response)
|
request.process_response('error', transaction_response)
|
||||||
request.db_set('status', 'Completed')
|
|
||||||
except Exception:
|
except Exception:
|
||||||
request.db_set('error', transaction_response)
|
request.process_response('error', transaction_response)
|
||||||
request.db_set('status', 'Failed')
|
|
||||||
frappe.log_error(frappe.get_traceback())
|
frappe.log_error(frappe.get_traceback())
|
||||||
|
|
||||||
else:
|
else:
|
||||||
request.db_set('error', transaction_response)
|
request.process_response('error', transaction_response)
|
||||||
request.db_set('status', 'Failed')
|
|
||||||
|
|
||||||
frappe.publish_realtime('process_phone_payment', after_commit=True, user=request.owner, message=transaction_response)
|
frappe.publish_realtime('process_phone_payment', after_commit=True, doctype=transaction_data.reference_doctype,
|
||||||
|
docname=transaction_data.reference_docname, user=request.owner, message=transaction_response)
|
||||||
|
|
||||||
|
def get_account_balance(request_payload):
|
||||||
|
""" Call account balance API to send the request to the Mpesa Servers """
|
||||||
|
try:
|
||||||
|
mpesa_settings = frappe.get_doc("Mpesa Settings", request_payload.get("reference_docname"))
|
||||||
|
env = "production" if not mpesa_settings.sandbox else "sandbox"
|
||||||
|
connector = MpesaConnector(env=env,
|
||||||
|
app_key=mpesa_settings.consumer_key,
|
||||||
|
app_secret=mpesa_settings.get_password("consumer_secret"))
|
||||||
|
|
||||||
|
# callback_url = get_request_site_address(True) + "/api/method/erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_settings.process_balance_info"
|
||||||
|
callback_url = "https://b014ca8e7957.ngrok.io/api/method/erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_settings.process_balance_info"
|
||||||
|
|
||||||
|
response = connector.get_balance(mpesa_settings.initiator_name, mpesa_settings.security_credential, mpesa_settings.till_number, 4, mpesa_settings.name, callback_url, callback_url)
|
||||||
|
return response
|
||||||
|
except Exception:
|
||||||
|
frappe.log_error(title=_("Account Balance Processing Error"))
|
||||||
|
frappe.throw(title=_("Error"), message=_("Please check your configuration and try again"))
|
||||||
|
|
||||||
|
@frappe.whitelist(allow_guest=True)
|
||||||
|
def process_balance_info(**kwargs):
|
||||||
|
|
||||||
|
account_balance_response = frappe._dict(kwargs["Result"])
|
||||||
|
|
||||||
|
conversation_id = getattr(account_balance_response, "ConversationID", "")
|
||||||
|
request = frappe.get_doc("Integration Request", conversation_id)
|
||||||
|
|
||||||
|
if request.status == "Completed":
|
||||||
|
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"]
|
||||||
|
for param in result_params:
|
||||||
|
if param["Key"] == "AccountBalance":
|
||||||
|
balance_info = param["Value"]
|
||||||
|
balance_info = convert_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)
|
||||||
|
|
||||||
|
request.process_response('output', account_balance_response)
|
||||||
|
except:
|
||||||
|
request.process_response('error', account_balance_response)
|
||||||
|
frappe.log_error(title=_("Mpesa Account Balance Processing Error"), message=account_balance_response)
|
||||||
|
else:
|
||||||
|
request.process_response('error', account_balance_response)
|
||||||
|
|
||||||
|
def convert_to_json(balance_info):
|
||||||
|
balance_dict = frappe._dict()
|
||||||
|
for account_info in balance_info.split("&"):
|
||||||
|
account_info = account_info.split('|')
|
||||||
|
balance_dict[account_info[0]] = dict(
|
||||||
|
current_balance=account_info[2],
|
||||||
|
available_balance=account_info[3],
|
||||||
|
reserved_balance=account_info[4],
|
||||||
|
uncleared_balance=account_info[5]
|
||||||
|
)
|
||||||
|
return dumps(balance_dict)
|
||||||
Reference in New Issue
Block a user