fix: handle payment completion via payment request
This commit is contained in:
parent
1bef6a530c
commit
8d54d61c28
@ -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")
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
@ -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,10 +155,14 @@ class PaymentRequest(Document):
|
|||||||
})
|
})
|
||||||
|
|
||||||
def set_as_paid(self):
|
def set_as_paid(self):
|
||||||
payment_entry = self.create_payment_entry()
|
if self.payment_channel == "Phone":
|
||||||
self.make_invoice()
|
self.db_set("status", "Paid")
|
||||||
|
|
||||||
return payment_entry
|
else:
|
||||||
|
payment_entry = self.create_payment_entry()
|
||||||
|
self.make_invoice()
|
||||||
|
|
||||||
|
return payment_entry
|
||||||
|
|
||||||
def create_payment_entry(self, submit=True):
|
def create_payment_entry(self, submit=True):
|
||||||
"""create entry"""
|
"""create entry"""
|
||||||
@ -269,7 +274,7 @@ class PaymentRequest(Document):
|
|||||||
|
|
||||||
# if shopping cart enabled and in session
|
# if shopping cart enabled and in session
|
||||||
if (shopping_cart_settings.enabled and hasattr(frappe.local, "session")
|
if (shopping_cart_settings.enabled and hasattr(frappe.local, "session")
|
||||||
and frappe.local.session.user != "Guest") and self.payment_channel != "Phone":
|
and frappe.local.session.user != "Guest") and self.payment_channel != "Phone":
|
||||||
|
|
||||||
success_url = shopping_cart_settings.payment_success_url
|
success_url = shopping_cart_settings.payment_success_url
|
||||||
if success_url:
|
if success_url:
|
||||||
@ -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),
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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."""
|
||||||
|
|||||||
@ -9,8 +9,8 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init_component() {
|
init_component() {
|
||||||
this.prepare_dom();
|
this.prepare_dom();
|
||||||
this.initialize_numpad();
|
this.initialize_numpad();
|
||||||
this.bind_events();
|
this.bind_events();
|
||||||
this.attach_shortcuts();
|
this.attach_shortcuts();
|
||||||
|
|
||||||
@ -18,32 +18,32 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
|
|
||||||
prepare_dom() {
|
prepare_dom() {
|
||||||
this.wrapper.append(
|
this.wrapper.append(
|
||||||
`<section class="col-span-6 flex shadow rounded payment-section bg-white mx-h-70 h-100 d-none">
|
`<section class="col-span-6 flex shadow rounded payment-section bg-white mx-h-70 h-100 d-none">
|
||||||
<div class="flex flex-col p-16 pt-8 pb-8 w-full">
|
<div class="flex flex-col p-16 pt-8 pb-8 w-full">
|
||||||
<div class="text-grey mb-6 payment-section no-select pointer">
|
<div class="text-grey mb-6 payment-section no-select pointer">
|
||||||
PAYMENT METHOD<span class="octicon octicon-chevron-down collapse-indicator"></span>
|
PAYMENT METHOD<span class="octicon octicon-chevron-down collapse-indicator"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="payment-modes flex flex-wrap"></div>
|
<div class="payment-modes flex flex-wrap"></div>
|
||||||
<div class="invoice-details-section"></div>
|
<div class="invoice-details-section"></div>
|
||||||
<div class="flex mt-auto justify-center w-full">
|
<div class="flex mt-auto justify-center w-full">
|
||||||
<div class="flex flex-col justify-center flex-1 ml-4">
|
<div class="flex flex-col justify-center flex-1 ml-4">
|
||||||
<div class="flex w-full">
|
<div class="flex w-full">
|
||||||
<div class="totals-remarks items-end justify-end flex flex-1">
|
<div class="totals-remarks items-end justify-end flex flex-1">
|
||||||
<div class="remarks text-md-0 text-grey mr-auto"></div>
|
<div class="remarks text-md-0 text-grey mr-auto"></div>
|
||||||
<div class="totals flex justify-end pt-4"></div>
|
<div class="totals flex justify-end pt-4"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="number-pad w-40 mb-4 ml-8 d-none"></div>
|
<div class="number-pad w-40 mb-4 ml-8 d-none"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-center mt-4 submit-order h-16 w-full rounded bg-primary text-md text-white no-select pointer text-bold">
|
<div class="flex items-center justify-center mt-4 submit-order h-16 w-full rounded bg-primary text-md text-white no-select pointer text-bold">
|
||||||
Complete Order
|
Complete Order
|
||||||
</div>
|
</div>
|
||||||
<div class="order-time flex items-center justify-end mt-2 pt-2 pb-2 w-full text-md-0 text-grey no-select pointer d-none"></div>
|
<div class="order-time flex items-center justify-end mt-2 pt-2 pb-2 w-full text-md-0 text-grey no-select pointer d-none"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>`
|
</section>`
|
||||||
)
|
)
|
||||||
this.$component = this.wrapper.find('.payment-section');
|
this.$component = this.wrapper.find('.payment-section');
|
||||||
this.$payment_modes = this.$component.find('.payment-modes');
|
this.$payment_modes = this.$component.find('.payment-modes');
|
||||||
this.$totals_remarks = this.$component.find('.totals-remarks');
|
this.$totals_remarks = this.$component.find('.totals-remarks');
|
||||||
this.$totals = this.$component.find('.totals');
|
this.$totals = this.$component.find('.totals');
|
||||||
@ -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({
|
||||||
@ -527,5 +527,5 @@ erpnext.PointOfSale.Payment = class {
|
|||||||
|
|
||||||
toggle_component(show) {
|
toggle_component(show) {
|
||||||
show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none');
|
show ? this.$component.removeClass('d-none') : this.$component.addClass('d-none');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user