fix: handle payment completion via payment request

This commit is contained in:
Mangesh-Khairnar 2020-10-05 22:37:31 +05:30
parent 1bef6a530c
commit 8d54d61c28
6 changed files with 66 additions and 45 deletions

View File

@ -950,12 +950,12 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
@frappe.whitelist() @frappe.whitelist()
def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None, mode_of_payment=None): def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
doc = frappe.get_doc(dt, dn) doc = frappe.get_doc(dt, dn)
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0: if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
frappe.throw(_("Can only make payment against unbilled {0}").format(dt)) frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
if dt in ("Sales Invoice", "Sales Order", "Dunning", "POS Invoice"): if dt in ("Sales Invoice", "Sales Order", "Dunning"):
party_type = "Customer" party_type = "Customer"
elif dt in ("Purchase Invoice", "Purchase Order"): elif dt in ("Purchase Invoice", "Purchase Order"):
party_type = "Supplier" party_type = "Supplier"
@ -965,7 +965,7 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
party_type = "Student" party_type = "Student"
# party account # party account
if dt in ["Sales Invoice", "POS Invoice"]: if dt == "Sales Invoice":
party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to
elif dt == "Purchase Invoice": elif dt == "Purchase Invoice":
party_account = doc.credit_to party_account = doc.credit_to
@ -984,7 +984,7 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account) party_account_currency = doc.get("party_account_currency") or get_account_currency(party_account)
# payment type # payment type
if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning", "POS Invoice") and doc.outstanding_amount > 0)) \ if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
or (dt=="Purchase Invoice" and doc.outstanding_amount < 0): or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
payment_type = "Receive" payment_type = "Receive"
else: else:
@ -994,7 +994,7 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
grand_total = outstanding_amount = 0 grand_total = outstanding_amount = 0
if party_amount: if party_amount:
grand_total = outstanding_amount = party_amount grand_total = outstanding_amount = party_amount
elif dt in ("Sales Invoice", "Purchase Invoice", "POS Invoice"): elif dt in ("Sales Invoice", "Purchase Invoice"):
if party_account_currency == doc.company_currency: if party_account_currency == doc.company_currency:
grand_total = doc.base_rounded_total or doc.base_grand_total grand_total = doc.base_rounded_total or doc.base_grand_total
else: else:
@ -1021,11 +1021,11 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
outstanding_amount = grand_total - flt(doc.advance_paid) outstanding_amount = grand_total - flt(doc.advance_paid)
# bank or cash # bank or cash
bank = get_default_bank_cash_account(doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment", mode_of_payment), bank = get_default_bank_cash_account(doc.company, "Bank", mode_of_payment=doc.get("mode_of_payment"),
account=bank_account) account=bank_account)
if not bank: if not bank:
bank = get_default_bank_cash_account(doc.company, "Cash", mode_of_payment=doc.get("mode_of_payment", mode_of_payment), bank = get_default_bank_cash_account(doc.company, "Cash", mode_of_payment=doc.get("mode_of_payment"),
account=bank_account) account=bank_account)
paid_amount = received_amount = 0 paid_amount = received_amount = 0
@ -1050,7 +1050,7 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
pe.company = doc.company pe.company = doc.company
pe.cost_center = doc.get("cost_center") pe.cost_center = doc.get("cost_center")
pe.posting_date = nowdate() pe.posting_date = nowdate()
pe.mode_of_payment = doc.get("mode_of_payment", mode_of_payment) pe.mode_of_payment = doc.get("mode_of_payment")
pe.party_type = party_type pe.party_type = party_type
pe.party = doc.get(scrub(party_type)) pe.party = doc.get(scrub(party_type))
pe.contact_person = doc.get("contact_person") pe.contact_person = doc.get("contact_person")

View File

@ -25,7 +25,7 @@ frappe.ui.form.on("Payment Request", "onload", function(frm, dt, dn){
}) })
frappe.ui.form.on("Payment Request", "refresh", function(frm) { frappe.ui.form.on("Payment Request", "refresh", function(frm) {
if(frm.doc.payment_request_type == 'Inward' && if(frm.doc.payment_request_type == 'Inward' && frm.doc.payment_channel === "Phone" &&
!in_list(["Initiated", "Paid"], frm.doc.status) && !frm.doc.__islocal && frm.doc.docstatus==1){ !in_list(["Initiated", "Paid"], frm.doc.status) && !frm.doc.__islocal && frm.doc.docstatus==1){
frm.add_custom_button(__('Resend Payment Email'), function(){ frm.add_custom_button(__('Resend Payment Email'), function(){
frappe.call({ frappe.call({

View File

@ -86,6 +86,7 @@ class PaymentRequest(Document):
payment_record = dict( payment_record = dict(
reference_doctype="Payment Request", reference_doctype="Payment Request",
reference_docname=self.name, reference_docname=self.name,
payment_reference=self.reference_name,
grand_total=self.grand_total, grand_total=self.grand_total,
sender=self.email_to, sender=self.email_to,
currency=self.currency, currency=self.currency,
@ -154,6 +155,10 @@ class PaymentRequest(Document):
}) })
def set_as_paid(self): def set_as_paid(self):
if self.payment_channel == "Phone":
self.db_set("status", "Paid")
else:
payment_entry = self.create_payment_entry() payment_entry = self.create_payment_entry()
self.make_invoice() self.make_invoice()
@ -332,6 +337,7 @@ def make_payment_request(**args):
"payment_request_type": args.get("payment_request_type"), "payment_request_type": args.get("payment_request_type"),
"currency": ref_doc.currency, "currency": ref_doc.currency,
"grand_total": grand_total, "grand_total": grand_total,
"mode_of_payment": args.mode_of_payment,
"email_to": args.recipient_id or ref_doc.owner, "email_to": args.recipient_id or ref_doc.owner,
"subject": _("Payment Request for {0}").format(args.dn), "subject": _("Payment Request for {0}").format(args.dn),
"message": gateway_account.get("message") or get_dummy_message(ref_doc), "message": gateway_account.get("message") or get_dummy_message(ref_doc),

View File

@ -58,6 +58,7 @@ class POSInvoice(SalesInvoice):
against_psi_doc.make_loyalty_point_entry() against_psi_doc.make_loyalty_point_entry()
if self.redeem_loyalty_points and self.loyalty_points: if self.redeem_loyalty_points and self.loyalty_points:
self.apply_loyalty_points() self.apply_loyalty_points()
self.check_phone_payments()
self.set_status(update=True) self.set_status(update=True)
def on_cancel(self): def on_cancel(self):
@ -70,6 +71,18 @@ class POSInvoice(SalesInvoice):
against_psi_doc.delete_loyalty_point_entry() against_psi_doc.delete_loyalty_point_entry()
against_psi_doc.make_loyalty_point_entry() against_psi_doc.make_loyalty_point_entry()
def check_phone_payments(self):
for pay in self.payments:
if pay.type == "Phone" and pay.amount >= 0:
paid_amt = frappe.db.get_value("Payment Request",
filters=dict(
reference_doctype="POS Invoice", reference_name=self.name,
mode_of_payment=pay.mode_of_payment, status="Paid"),
fieldname="grand_total")
if pay.amount != paid_amt:
return frappe.throw(_("Payment related to {0} is not completed").format(pay.mode_of_payment))
def validate_stock_availablility(self): def validate_stock_availablility(self):
allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock') allow_negative_stock = frappe.db.get_value('Stock Settings', None, 'allow_negative_stock')
@ -333,6 +346,7 @@ class POSInvoice(SalesInvoice):
"payment_request_type": "Inward", "payment_request_type": "Inward",
"party_type": "Customer", "party_type": "Customer",
"party": self.customer, "party": self.customer,
"mode_of_payment": pay.mode_of_payment,
"recipient_id": self.contact_mobile, "recipient_id": self.contact_mobile,
"submit_doc": True "submit_doc": True
} }

View File

@ -76,7 +76,7 @@ def generate_stk_push(**kwargs):
response = connector.stk_push(business_shortcode=mpesa_settings.till_number, response = connector.stk_push(business_shortcode=mpesa_settings.till_number,
passcode=mpesa_settings.get_password("online_passkey"), amount=args.grand_total, passcode=mpesa_settings.get_password("online_passkey"), amount=args.grand_total,
callback_url=callback_url, reference_code=args.payment_request_name, callback_url=callback_url, reference_code=mpesa_settings.till_number,
phone_number=args.sender, description="POS Payment") phone_number=args.sender, description="POS Payment")
return response return response
@ -100,14 +100,15 @@ def verify_transaction(**kwargs):
transaction_data = frappe._dict(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 request.reference_doctype and request.reference_docname:
try: try:
doc = frappe.get_doc(transaction_data.reference_doctype, doc = frappe.get_doc(request.reference_doctype,
transaction_data.reference_docname).run_method("on_payment_authorized", 'Completed') request.reference_docname)
doc.run_method("on_payment_authorized", 'Completed')
item_response = transaction_response["CallbackMetadata"]["Item"] item_response = transaction_response["CallbackMetadata"]["Item"]
mpesa_receipt = fetch_param_value(item_response, "MpesaReceiptNumber", "Name") mpesa_receipt = fetch_param_value(item_response, "MpesaReceiptNumber", "Name")
frappe.db.set_value("POS Invoice", doc.reference_docname, "mpesa_receipt_number", mpesa_receipt) frappe.db.set_value("POS Invoice", doc.reference_name, "mpesa_receipt_number", mpesa_receipt)
request.process_response('output', transaction_response) request.process_response('output', transaction_response)
except Exception: except Exception:
request.process_response('error', transaction_response) request.process_response('error', transaction_response)
@ -116,8 +117,8 @@ def verify_transaction(**kwargs):
else: else:
request.process_response('error', transaction_response) request.process_response('error', transaction_response)
frappe.publish_realtime('process_phone_payment', after_commit=True, doctype=transaction_data.reference_doctype, frappe.publish_realtime('process_phone_payment', doctype="POS Invoice",
docname=transaction_data.reference_docname, user=request.owner, message=transaction_response) docname=transaction_data.payment_reference, user=request.owner, message=transaction_response)
def get_account_balance(request_payload): def get_account_balance(request_payload):
"""Call account balance API to send the request to the Mpesa Servers.""" """Call account balance API to send the request to the Mpesa Servers."""

View File

@ -174,16 +174,16 @@ erpnext.PointOfSale.Payment = class {
} }
}) })
frappe.realtime.on("process_phone_payments", function(data) { frappe.realtime.on("process_phone_payment", function(data) {
console.log('within')
frappe.dom.unfreeze(); frappe.dom.unfreeze();
let message = data["ResultDesc"]; let message = data["ResultDesc"];
let title = __("Payment Failed"); let title = __("Payment Failed");
const frm = me.events.get_frm();
if (data["ResultCode"] == 0) { if (data["ResultCode"] == 0) {
title = __("Payment Received"); title = __("Payment Received");
$('[data-fieldname=request_for_payment]').text("Paid") $('.btn.btn-xs.btn-default[data-fieldname=request_for_payment]').html(`Payment Received`)
cur_pos.submit() me.events.submit_invoice();
} }
frappe.msgprint({ frappe.msgprint({