Merge branch 'develop' into rfq-contact-email-set

This commit is contained in:
Marica 2021-02-11 14:45:09 +05:30 committed by GitHub
commit 819b8d0266
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 436 additions and 132 deletions

View File

@ -3,6 +3,7 @@
# For license information, please see license.txt
from __future__ import unicode_literals
import json
import frappe
from frappe import _
from frappe.model.document import Document
@ -82,18 +83,37 @@ class PaymentRequest(Document):
self.make_communication_entry()
elif self.payment_channel == "Phone":
controller = get_payment_gateway_controller(self.payment_gateway)
payment_record = dict(
reference_doctype="Payment Request",
reference_docname=self.name,
payment_reference=self.reference_name,
grand_total=self.grand_total,
sender=self.email_to,
currency=self.currency,
payment_gateway=self.payment_gateway
)
controller.validate_transaction_currency(self.currency)
controller.request_for_payment(**payment_record)
self.request_phone_payment()
def request_phone_payment(self):
controller = get_payment_gateway_controller(self.payment_gateway)
request_amount = self.get_request_amount()
payment_record = dict(
reference_doctype="Payment Request",
reference_docname=self.name,
payment_reference=self.reference_name,
request_amount=request_amount,
sender=self.email_to,
currency=self.currency,
payment_gateway=self.payment_gateway
)
controller.validate_transaction_currency(self.currency)
controller.request_for_payment(**payment_record)
def get_request_amount(self):
data_of_completed_requests = frappe.get_all("Integration Request", filters={
'reference_doctype': self.doctype,
'reference_docname': self.name,
'status': 'Completed'
}, pluck="data")
if not data_of_completed_requests:
return self.grand_total
request_amounts = sum([json.loads(d).get('request_amount') for d in data_of_completed_requests])
return request_amounts
def on_cancel(self):
self.check_if_payment_entry_exists()
@ -351,8 +371,8 @@ def make_payment_request(**args):
if args.order_type == "Shopping Cart" or args.mute_email:
pr.flags.mute_email = True
pr.insert(ignore_permissions=True)
if args.submit_doc:
pr.insert(ignore_permissions=True)
pr.submit()
if args.order_type == "Shopping Cart":
@ -412,8 +432,8 @@ def get_existing_payment_request_amount(ref_dt, ref_dn):
def get_gateway_details(args):
"""return gateway and payment account of default payment gateway"""
if args.get("payment_gateway"):
return get_payment_gateway_account(args.get("payment_gateway"))
if args.get("payment_gateway_account"):
return get_payment_gateway_account(args.get("payment_gateway_account"))
if args.order_type == "Shopping Cart":
payment_gateway_account = frappe.get_doc("Shopping Cart Settings").payment_gateway_account

View File

@ -45,7 +45,8 @@ class TestPaymentRequest(unittest.TestCase):
def test_payment_request_linkings(self):
so_inr = make_sales_order(currency="INR")
pr = make_payment_request(dt="Sales Order", dn=so_inr.name, recipient_id="saurabh@erpnext.com")
pr = make_payment_request(dt="Sales Order", dn=so_inr.name, recipient_id="saurabh@erpnext.com",
payment_gateway_account="_Test Gateway - INR")
self.assertEqual(pr.reference_doctype, "Sales Order")
self.assertEqual(pr.reference_name, so_inr.name)
@ -54,7 +55,8 @@ class TestPaymentRequest(unittest.TestCase):
conversion_rate = get_exchange_rate("USD", "INR")
si_usd = create_sales_invoice(currency="USD", conversion_rate=conversion_rate)
pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com")
pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com",
payment_gateway_account="_Test Gateway - USD")
self.assertEqual(pr.reference_doctype, "Sales Invoice")
self.assertEqual(pr.reference_name, si_usd.name)
@ -68,7 +70,7 @@ class TestPaymentRequest(unittest.TestCase):
so_inr = make_sales_order(currency="INR")
pr = make_payment_request(dt="Sales Order", dn=so_inr.name, recipient_id="saurabh@erpnext.com",
mute_email=1, submit_doc=1, return_doc=1)
mute_email=1, payment_gateway_account="_Test Gateway - INR", submit_doc=1, return_doc=1)
pe = pr.set_as_paid()
so_inr = frappe.get_doc("Sales Order", so_inr.name)
@ -79,7 +81,7 @@ class TestPaymentRequest(unittest.TestCase):
currency="USD", conversion_rate=50)
pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com",
mute_email=1, payment_gateway="_Test Gateway - USD", submit_doc=1, return_doc=1)
mute_email=1, payment_gateway_account="_Test Gateway - USD", submit_doc=1, return_doc=1)
pe = pr.set_as_paid()
@ -106,7 +108,7 @@ class TestPaymentRequest(unittest.TestCase):
currency="USD", conversion_rate=50)
pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com",
mute_email=1, payment_gateway="_Test Gateway - USD", submit_doc=1, return_doc=1)
mute_email=1, payment_gateway_account="_Test Gateway - USD", submit_doc=1, return_doc=1)
pe = pr.create_payment_entry()
pr.load_from_db()

View File

@ -21,7 +21,7 @@ frappe.ui.form.on('POS Closing Entry', {
return { filters: { 'status': 'Open', 'docstatus': 1 } };
});
if (frm.doc.docstatus === 0) frm.set_value("period_end_date", frappe.datetime.now_datetime());
if (frm.doc.docstatus === 0 && !frm.doc.amended_from) frm.set_value("period_end_date", frappe.datetime.now_datetime());
if (frm.doc.docstatus === 1) set_html_data(frm);
},

View File

@ -195,18 +195,43 @@ frappe.ui.form.on('POS Invoice', {
},
request_for_payment: function (frm) {
if (!frm.doc.contact_mobile) {
frappe.throw(__('Please enter mobile number first.'));
}
frm.dirty();
frm.save().then(() => {
frappe.dom.freeze();
frappe.call({
method: 'create_payment_request',
doc: frm.doc,
})
frappe.dom.freeze(__('Waiting for payment...'));
frappe
.call({
method: 'create_payment_request',
doc: frm.doc
})
.fail(() => {
frappe.dom.unfreeze();
frappe.msgprint('Payment request failed');
frappe.msgprint(__('Payment request failed'));
})
.then(() => {
frappe.msgprint('Payment request sent successfully');
.then(({ message }) => {
const payment_request_name = message.name;
setTimeout(() => {
frappe.db.get_value('Payment Request', payment_request_name, ['status', 'grand_total']).then(({ message }) => {
if (message.status != 'Paid') {
frappe.dom.unfreeze();
frappe.msgprint({
message: __('Payment Request took too long to respond. Please try requesting for payment again.'),
title: __('Request Timeout')
});
} else if (frappe.dom.freeze_count != 0) {
frappe.dom.unfreeze();
cur_frm.reload_doc();
cur_pos.payment.events.submit_invoice();
frappe.show_alert({
message: __("Payment of {0} received successfully.", [format_currency(message.grand_total, frm.doc.currency, 0)]),
indicator: 'green'
});
}
});
}, 60000);
});
});
}

View File

@ -384,22 +384,48 @@ class POSInvoice(SalesInvoice):
if not self.contact_mobile:
frappe.throw(_("Please enter the phone number first"))
payment_gateway = frappe.db.get_value("Payment Gateway Account", {
"payment_account": pay.account,
})
record = {
"payment_gateway": payment_gateway,
"dt": "POS Invoice",
"dn": self.name,
"payment_request_type": "Inward",
"party_type": "Customer",
"party": self.customer,
"mode_of_payment": pay.mode_of_payment,
"recipient_id": self.contact_mobile,
"submit_doc": True
}
pay_req = self.get_existing_payment_request(pay)
if not pay_req:
pay_req = self.get_new_payment_request(pay)
pay_req.submit()
else:
pay_req.request_phone_payment()
return make_payment_request(**record)
return pay_req
def get_new_payment_request(self, mop):
payment_gateway_account = frappe.db.get_value("Payment Gateway Account", {
"payment_account": mop.account,
}, ["name"])
args = {
"dt": "POS Invoice",
"dn": self.name,
"recipient_id": self.contact_mobile,
"mode_of_payment": mop.mode_of_payment,
"payment_gateway_account": payment_gateway_account,
"payment_request_type": "Inward",
"party_type": "Customer",
"party": self.customer,
"return_doc": True
}
return make_payment_request(**args)
def get_existing_payment_request(self, pay):
payment_gateway_account = frappe.db.get_value("Payment Gateway Account", {
"payment_account": pay.account,
}, ["name"])
args = {
'doctype': 'Payment Request',
'reference_doctype': 'POS Invoice',
'reference_name': self.name,
'payment_gateway_account': payment_gateway_account,
'email_to': self.contact_mobile
}
pr = frappe.db.exists(args)
if pr:
return frappe.get_doc('Payment Request', pr[0][0])
@frappe.whitelist()
def get_stock_availability(item_code, warehouse):

View File

@ -446,9 +446,13 @@ class SellingController(StockController):
check_list, chk_dupl_itm = [], []
if cint(frappe.db.get_single_value("Selling Settings", "allow_multiple_items")):
return
if self.doctype == "Sales Invoice" and self.is_consolidated:
return
if self.doctype == "POS Invoice":
return
for d in self.get('items'):
if self.doctype in ["POS Invoice","Sales Invoice"]:
if self.doctype == "Sales Invoice":
stock_items = [d.item_code, d.description, d.warehouse, d.sales_order or d.delivery_note, d.batch_no or '']
non_stock_items = [d.item_code, d.description, d.sales_order or d.delivery_note]
elif self.doctype == "Delivery Note":

View File

@ -5,7 +5,7 @@ import datetime
class MpesaConnector():
def __init__(self, env="sandbox", app_key=None, app_secret=None, sandbox_url="https://sandbox.safaricom.co.ke",
live_url="https://safaricom.co.ke"):
live_url="https://api.safaricom.co.ke"):
"""Setup configuration for Mpesa connector and generate new access token."""
self.env = env
self.app_key = app_key
@ -102,14 +102,14 @@ class MpesaConnector():
"BusinessShortCode": business_shortcode,
"Password": encoded.decode("utf-8"),
"Timestamp": time,
"TransactionType": "CustomerPayBillOnline",
"Amount": amount,
"PartyA": int(phone_number),
"PartyB": business_shortcode,
"PartyB": reference_code,
"PhoneNumber": int(phone_number),
"CallBackURL": callback_url,
"AccountReference": reference_code,
"TransactionDesc": description
"TransactionDesc": description,
"TransactionType": "CustomerPayBillOnline" if self.env == "sandbox" else "CustomerBuyGoodsOnline"
}
headers = {'Authorization': 'Bearer {0}'.format(self.authentication_token), 'Content-Type': "application/json"}

View File

@ -11,8 +11,10 @@
"consumer_secret",
"initiator_name",
"till_number",
"transaction_limit",
"sandbox",
"column_break_4",
"business_shortcode",
"online_passkey",
"security_credential",
"get_account_balance",
@ -84,10 +86,24 @@
"fieldname": "get_account_balance",
"fieldtype": "Button",
"label": "Get Account Balance"
},
{
"depends_on": "eval:(doc.sandbox==0)",
"fieldname": "business_shortcode",
"fieldtype": "Data",
"label": "Business Shortcode",
"mandatory_depends_on": "eval:(doc.sandbox==0)"
},
{
"default": "150000",
"fieldname": "transaction_limit",
"fieldtype": "Float",
"label": "Transaction Limit",
"non_negative": 1
}
],
"links": [],
"modified": "2020-09-25 20:21:38.215494",
"modified": "2021-01-29 12:02:16.106942",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Mpesa Settings",

View File

@ -33,13 +33,34 @@ class MpesaSettings(Document):
create_mode_of_payment('Mpesa-' + self.payment_gateway_name, payment_type="Phone")
def request_for_payment(self, **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))
args = frappe._dict(kwargs)
request_amounts = self.split_request_amount_according_to_transaction_limit(args)
self.handle_api_response("CheckoutRequestID", kwargs, response)
for i, amount in enumerate(request_amounts):
args.request_amount = amount
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(amount))
else:
response = frappe._dict(generate_stk_push(**args))
self.handle_api_response("CheckoutRequestID", args, response)
def split_request_amount_according_to_transaction_limit(self, args):
request_amount = args.request_amount
if request_amount > self.transaction_limit:
# make multiple requests
request_amounts = []
requests_to_be_made = frappe.utils.ceil(request_amount / self.transaction_limit) # 480/150 = ceil(3.2) = 4
for i in range(requests_to_be_made):
amount = self.transaction_limit
if i == requests_to_be_made - 1:
amount = request_amount - (self.transaction_limit * i) # for 4th request, 480 - (150 * 3) = 30
request_amounts.append(amount)
else:
request_amounts = [request_amount]
return request_amounts
def get_account_balance_info(self):
payload = dict(
@ -67,7 +88,8 @@ class MpesaSettings(Document):
req_name = getattr(response, global_id)
error = None
create_request_log(request_dict, "Host", "Mpesa", req_name, error)
if not frappe.db.exists('Integration Request', req_name):
create_request_log(request_dict, "Host", "Mpesa", req_name, error)
if error:
frappe.throw(_(getattr(response, "errorMessage")), title=_("Transaction Error"))
@ -80,6 +102,8 @@ def generate_stk_push(**kwargs):
mpesa_settings = frappe.get_doc("Mpesa Settings", args.payment_gateway[6:])
env = "production" if not mpesa_settings.sandbox else "sandbox"
# for sandbox, business shortcode is same as till number
business_shortcode = mpesa_settings.business_shortcode if env == "production" else mpesa_settings.till_number
connector = MpesaConnector(env=env,
app_key=mpesa_settings.consumer_key,
@ -87,10 +111,12 @@ def generate_stk_push(**kwargs):
mobile_number = sanitize_mobile_number(args.sender)
response = connector.stk_push(business_shortcode=mpesa_settings.till_number,
passcode=mpesa_settings.get_password("online_passkey"), amount=args.grand_total,
response = connector.stk_push(
business_shortcode=business_shortcode, amount=args.request_amount,
passcode=mpesa_settings.get_password("online_passkey"),
callback_url=callback_url, reference_code=mpesa_settings.till_number,
phone_number=mobile_number, description="POS Payment")
phone_number=mobile_number, description="POS Payment"
)
return response
@ -108,29 +134,72 @@ def verify_transaction(**kwargs):
transaction_response = frappe._dict(kwargs["Body"]["stkCallback"])
checkout_id = getattr(transaction_response, "CheckoutRequestID", "")
request = frappe.get_doc("Integration Request", checkout_id)
transaction_data = frappe._dict(loads(request.data))
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 request.reference_doctype and request.reference_docname:
if integration_request.reference_doctype and integration_request.reference_docname:
try:
doc = frappe.get_doc(request.reference_doctype,
request.reference_docname)
doc.run_method("on_payment_authorized", 'Completed')
item_response = transaction_response["CallbackMetadata"]["Item"]
amount = fetch_param_value(item_response, "Amount", "Name")
mpesa_receipt = fetch_param_value(item_response, "MpesaReceiptNumber", "Name")
frappe.db.set_value("POS Invoice", doc.reference_name, "mpesa_receipt_number", mpesa_receipt)
request.handle_success(transaction_response)
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:
request.handle_failure(transaction_response)
integration_request.handle_failure(transaction_response)
frappe.log_error(frappe.get_traceback())
else:
request.handle_failure(transaction_response)
integration_request.handle_failure(transaction_response)
frappe.publish_realtime('process_phone_payment', doctype="POS Invoice",
docname=transaction_data.payment_reference, user=request.owner, message=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, reference_docname, checkout_id):
output_of_other_completed_requests = frappe.get_all("Integration Request", filters={
'name': ['!=', checkout_id],
'reference_doctype': reference_doctype,
'reference_docname': reference_docname,
'status': 'Completed'
}, pluck="output")
mpesa_receipts, completed_payments = [], []
for out in output_of_other_completed_requests:
out = frappe._dict(loads(out))
item_response = out["CallbackMetadata"]["Item"]
completed_amount = fetch_param_value(item_response, "Amount", "Name")
completed_mpesa_receipt = fetch_param_value(item_response, "MpesaReceiptNumber", "Name")
completed_payments.append(completed_amount)
mpesa_receipts.append(completed_mpesa_receipt)
return mpesa_receipts, completed_payments
def get_account_balance(request_payload):
"""Call account balance API to send the request to the Mpesa Servers."""

View File

@ -9,6 +9,10 @@ from erpnext.erpnext_integrations.doctype.mpesa_settings.mpesa_settings import p
from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
class TestMpesaSettings(unittest.TestCase):
def tearDown(self):
frappe.db.sql('delete from `tabMpesa Settings`')
frappe.db.sql('delete from `tabIntegration Request` where integration_request_service = "Mpesa"')
def test_creation_of_payment_gateway(self):
create_mpesa_settings(payment_gateway_name="_Test")
@ -40,6 +44,8 @@ class TestMpesaSettings(unittest.TestCase):
}
}))
integration_request.delete()
def test_processing_of_callback_payload(self):
create_mpesa_settings(payment_gateway_name="Payment")
mpesa_account = frappe.db.get_value("Payment Gateway Account", {"payment_gateway": 'Mpesa-Payment'}, "payment_account")
@ -56,10 +62,16 @@ class TestMpesaSettings(unittest.TestCase):
# test payment request creation
self.assertEquals(pr.payment_gateway, "Mpesa-Payment")
callback_response = get_payment_callback_payload()
# submitting payment request creates integration requests with random id
integration_req_ids = frappe.get_all("Integration Request", filters={
'reference_doctype': pr.doctype,
'reference_docname': pr.name,
}, pluck="name")
callback_response = get_payment_callback_payload(Amount=500, CheckoutRequestID=integration_req_ids[0])
verify_transaction(**callback_response)
# test creation of integration request
integration_request = frappe.get_doc("Integration Request", "ws_CO_061020201133231972")
integration_request = frappe.get_doc("Integration Request", integration_req_ids[0])
# test integration request creation and successful update of the status on receiving callback response
self.assertTrue(integration_request)
@ -69,8 +81,120 @@ class TestMpesaSettings(unittest.TestCase):
integration_request.reload()
self.assertEquals(pos_invoice.mpesa_receipt_number, "LGR7OWQX0R")
self.assertEquals(integration_request.status, "Completed")
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
integration_request.delete()
pr.reload()
pr.cancel()
pr.delete()
pos_invoice.delete()
def test_processing_of_multiple_callback_payload(self):
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")
frappe.db.set_value("Mpesa Settings", "Payment", "transaction_limit", "500")
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "KES")
pos_invoice = create_pos_invoice(do_not_submit=1)
pos_invoice.append("payments", {'mode_of_payment': 'Mpesa-Payment', 'account': mpesa_account, 'amount': 1000})
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")
# submitting payment request creates integration requests with random id
integration_req_ids = frappe.get_all("Integration Request", filters={
'reference_doctype': pr.doctype,
'reference_docname': pr.name,
}, pluck="name")
# create random receipt nos and send it as response to callback handler
mpesa_receipt_numbers = [frappe.utils.random_string(5) for d in integration_req_ids]
integration_requests = []
for i in range(len(integration_req_ids)):
callback_response = get_payment_callback_payload(
Amount=500,
CheckoutRequestID=integration_req_ids[i],
MpesaReceiptNumber=mpesa_receipt_numbers[i]
)
# handle response manually
verify_transaction(**callback_response)
# test completion of integration request
integration_request = frappe.get_doc("Integration Request", integration_req_ids[i])
self.assertEquals(integration_request.status, "Completed")
integration_requests.append(integration_request)
# check receipt number once all the integration requests are completed
pos_invoice.reload()
self.assertEquals(pos_invoice.mpesa_receipt_number, ', '.join(mpesa_receipt_numbers))
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
[d.delete() for d in integration_requests]
pr.reload()
pr.cancel()
pr.delete()
pos_invoice.delete()
def test_processing_of_only_one_succes_callback_payload(self):
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")
frappe.db.set_value("Mpesa Settings", "Payment", "transaction_limit", "500")
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "KES")
pos_invoice = create_pos_invoice(do_not_submit=1)
pos_invoice.append("payments", {'mode_of_payment': 'Mpesa-Payment', 'account': mpesa_account, 'amount': 1000})
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")
# submitting payment request creates integration requests with random id
integration_req_ids = frappe.get_all("Integration Request", filters={
'reference_doctype': pr.doctype,
'reference_docname': pr.name,
}, pluck="name")
# create random receipt nos and send it as response to callback handler
mpesa_receipt_numbers = [frappe.utils.random_string(5) for d in integration_req_ids]
callback_response = get_payment_callback_payload(
Amount=500,
CheckoutRequestID=integration_req_ids[0],
MpesaReceiptNumber=mpesa_receipt_numbers[0]
)
# handle response manually
verify_transaction(**callback_response)
# test completion of integration request
integration_request = frappe.get_doc("Integration Request", integration_req_ids[0])
self.assertEquals(integration_request.status, "Completed")
# now one request is completed
# second integration request fails
# now retrying payment request should make only one integration request again
pr = pos_invoice.create_payment_request()
new_integration_req_ids = frappe.get_all("Integration Request", filters={
'reference_doctype': pr.doctype,
'reference_docname': pr.name,
'name': ['not in', integration_req_ids]
}, pluck="name")
self.assertEquals(len(new_integration_req_ids), 1)
frappe.db.set_value("Customer", "_Test Customer", "default_currency", "")
frappe.db.sql("delete from `tabIntegration Request` where integration_request_service = 'Mpesa'")
pr.reload()
pr.cancel()
pr.delete()
pos_invoice.delete()
def create_mpesa_settings(payment_gateway_name="Express"):
if frappe.db.exists("Mpesa Settings", payment_gateway_name):
@ -160,16 +284,19 @@ def get_test_account_balance_response():
}
}
def get_payment_request_response_payload():
def get_payment_request_response_payload(Amount=500):
"""Response received after successfully calling the stk push process request API."""
CheckoutRequestID = frappe.utils.random_string(10)
return {
"MerchantRequestID": "8071-27184008-1",
"CheckoutRequestID": "ws_CO_061020201133231972",
"CheckoutRequestID": CheckoutRequestID,
"ResultCode": 0,
"ResultDesc": "The service request is processed successfully.",
"CallbackMetadata": {
"Item": [
{ "Name": "Amount", "Value": 500.0 },
{ "Name": "Amount", "Value": Amount },
{ "Name": "MpesaReceiptNumber", "Value": "LGR7OWQX0R" },
{ "Name": "TransactionDate", "Value": 20201006113336 },
{ "Name": "PhoneNumber", "Value": 254723575670 }
@ -177,41 +304,26 @@ def get_payment_request_response_payload():
}
}
def get_payment_callback_payload():
def get_payment_callback_payload(Amount=500, CheckoutRequestID="ws_CO_061020201133231972", MpesaReceiptNumber="LGR7OWQX0R"):
"""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
"stkCallback":{
"MerchantRequestID":"19465-780693-1",
"CheckoutRequestID":CheckoutRequestID,
"ResultCode":0,
"ResultDesc":"The service request is processed successfully.",
"CallbackMetadata":{
"Item":[
{ "Name":"Amount", "Value":Amount },
{ "Name":"MpesaReceiptNumber", "Value":MpesaReceiptNumber },
{ "Name":"Balance" },
{ "Name":"TransactionDate", "Value":20170727154800 },
{ "Name":"PhoneNumber", "Value":254721566839 }
]
}
]
}
}
}
}
def get_account_balance_callback_payload():

View File

@ -126,7 +126,9 @@ class Customer(TransactionBase):
'''If Customer created from Lead, update lead status to "Converted"
update Customer link in Quotation, Opportunity'''
if self.lead_name:
frappe.db.set_value('Lead', self.lead_name, 'status', 'Converted', update_modified=False)
lead = frappe.get_doc('Lead', self.lead_name)
lead.status = 'Converted'
lead.save()
def create_lead_address_contact(self):
if self.lead_name:

View File

@ -477,7 +477,7 @@ erpnext.PointOfSale.ItemCart = class {
const taxes = frm.doc.taxes.map(t => {
return {
description: t.description, rate: t.rate
}
};
});
this.render_taxes(frm.doc.total_taxes_and_charges, taxes);
}

View File

@ -153,32 +153,23 @@ erpnext.PointOfSale.Payment = class {
me.$payment_modes.find(`.${mode}-amount`).css('display', 'none');
me.$payment_modes.find(`.${mode}-name`).css('display', 'inline');
const doc = me.events.get_frm().doc;
me.selected_mode = me[`${mode}_control`];
me.selected_mode && me.selected_mode.$input.get(0).focus();
const current_value = me.selected_mode ? me.selected_mode.get_value() : undefined;
!current_value && doc.grand_total > doc.paid_amount && me.selected_mode ?
me.selected_mode.set_value(doc.grand_total - doc.paid_amount) : '';
me.auto_set_remaining_amount();
}
});
frappe.realtime.on("process_phone_payment", function(data) {
frappe.dom.unfreeze();
cur_frm.reload_doc();
let message = data["ResultDesc"];
let title = __("Payment Failed");
frappe.ui.form.on('POS Invoice', 'contact_mobile', (frm) => {
const contact = frm.doc.contact_mobile;
const request_button = $(this.request_for_payment_field.$input[0]);
if (contact) {
request_button.removeClass('btn-default').addClass('btn-primary');
} else {
request_button.removeClass('btn-primary').addClass('btn-default');
}
});
if (data["ResultCode"] == 0) {
title = __("Payment Received");
$('.btn.btn-xs.btn-default[data-fieldname=request_for_payment]').html(`Payment Received`);
me.events.submit_invoice();
}
frappe.msgprint({
"message": message,
"title": title
});
});
this.setup_listener_for_payments();
this.$payment_modes.on('click', '.shortcut', () => {
const value = $(this).attr('data-value');
@ -224,6 +215,41 @@ erpnext.PointOfSale.Payment = class {
});
}
setup_listener_for_payments() {
frappe.realtime.on("process_phone_payment", (data) => {
const doc = this.events.get_frm().doc;
const { response, amount, success, failure_message } = data;
let message, title;
if (success) {
title = __("Payment Received");
if (amount >= doc.grand_total) {
frappe.dom.unfreeze();
message = __("Payment of {0} received successfully.", [format_currency(amount, doc.currency, 0)]);
this.events.submit_invoice();
cur_frm.reload_doc();
} else {
message = __("Payment of {0} received successfully. Waiting for other requests to complete...", [format_currency(amount, doc.currency, 0)]);
}
} else if (failure_message) {
message = failure_message;
title = __("Payment Failed");
}
frappe.msgprint({ "message": message, "title": title });
});
}
auto_set_remaining_amount() {
const doc = this.events.get_frm().doc;
const remaining_amount = doc.grand_total - doc.paid_amount;
const current_value = this.selected_mode ? this.selected_mode.get_value() : undefined;
if (!current_value && remaining_amount > 0 && this.selected_mode) {
this.selected_mode.set_value(remaining_amount);
}
}
attach_shortcuts() {
const ctrl_label = frappe.utils.is_mac() ? '⌘' : 'Ctrl';
this.$component.find('.submit-order-btn').attr("title", `${ctrl_label}+Enter`);
@ -333,9 +359,11 @@ erpnext.PointOfSale.Payment = class {
fieldtype: 'Currency',
placeholder: __('Enter {0} amount.', [p.mode_of_payment]),
onchange: function() {
if (this.value || this.value == 0) {
frappe.model.set_value(p.doctype, p.name, 'amount', flt(this.value))
.then(() => me.update_totals_section());
const current_value = frappe.model.get_value(p.doctype, p.name, 'amount');
if (current_value != this.value) {
frappe.model
.set_value(p.doctype, p.name, 'amount', flt(this.value))
.then(() => me.update_totals_section())
const formatted_currency = format_currency(this.value, currency);
me.$payment_modes.find(`.${mode}-amount`).html(formatted_currency);