diff --git a/ns_app/api/payments.py b/ns_app/api/payments.py index a9f017f..61c022e 100644 --- a/ns_app/api/payments.py +++ b/ns_app/api/payments.py @@ -97,6 +97,31 @@ def get_collect_checkout_url(invoice): ) +@frappe.whitelist(allow_guest=True) +def crystalclear_webhook(): + data = frappe.local.form_dict + + # Validate success + if data.get("response") != "1": + return "ignored" + + invoice = data.get("orderid") + amount = data.get("amount") + transaction_id = data.get("transactionid") + + # Prevent duplicates + if frappe.db.exists("Payment Entry", {"reference_no": transaction_id}): + return "duplicate" + + create_payment_entry( + invoice=invoice, + amount=amount, + transaction_id=transaction_id + ) + + return "ok" + + def create_payment_entry(invoice, amount, transaction_id=None): inv = frappe.get_doc("Sales Invoice", invoice) diff --git a/ns_app/public/js/sales_invoice.js b/ns_app/public/js/sales_invoice.js index a8db35a..87ff0e8 100644 --- a/ns_app/public/js/sales_invoice.js +++ b/ns_app/public/js/sales_invoice.js @@ -2,42 +2,35 @@ frappe.ui.form.on("Sales Invoice", { refresh(frm) { frm.clear_custom_buttons(); - // Submitted invoices only if (frm.doc.docstatus !== 1) return; if (!frm.doc.customer) return; - // Only show manual payment button if AutoPay is OFF - frappe.db.get_value( - "Customer", - frm.doc.customer, - "auto_pay", - (r) => { - if (!r) return; + // If already paid, don't show button + if (frm.doc.outstanding_amount <= 0) { + frm.dashboard.add_indicator("Paid", "green"); + return; + } - if (!r.auto_pay) { - frm.add_custom_button( - "Run Payment", - () => run_payment_flow(frm), - "Actions" - ); - } - } - ); + frm.dashboard.add_indicator("Unpaid", "red"); + + frm.add_custom_button("Run Payment", () => { + run_payment_flow(frm); + }, "Actions"); } }); function run_payment_flow(frm) { + frm.disable_save(); + frappe.call({ method: "ns_app.api.payments.check_autopay", - args: { - customer: frm.doc.customer - }, + args: { customer: frm.doc.customer }, callback(r) { if (!r.message) return; if (r.message.autopay_enabled) { - run_autopay(frm); + run_autopay(frm, r.message.autopay_id); } else { open_manual_payment_form(frm); } @@ -46,57 +39,99 @@ function run_payment_flow(frm) { } -function run_autopay(frm) { - frappe.confirm( - `Run AutoPay for $${frm.doc.outstanding_amount}?`, - () => { - frappe.call({ - method: "ns_app.api.payments.run_autopay_payment", - args: { - invoice: frm.doc.name - }, - callback(r) { - frappe.msgprint(r.message || "Payment processed"); - frm.reload_doc(); - } - }); - } - ); -} - - -// Hosted checkout function open_manual_payment_form(frm) { + + const dialog = new frappe.ui.Dialog({ + title: "Secure Payment", + size: "large", + fields: [ + { + fieldtype: "HTML", + fieldname: "loader", + options: ` +
+ Loading secure checkout... +
+ ` + }, + { + fieldtype: "HTML", + fieldname: "payment_form", + options: `
` + } + ], + primary_action_label: "Close", + primary_action() { + dialog.hide(); + frm.reload_doc(); + } + }); + + dialog.show(); + frappe.call({ method: "ns_app.api.payments.get_collect_checkout_url", - args: { - invoice: frm.doc.name - }, + args: { invoice: frm.doc.name }, callback(r) { if (!r.message) { frappe.msgprint("Unable to start payment"); return; } - const dialog = new frappe.ui.Dialog({ - title: "Secure Payment", - size: "large", - fields: [ - { - fieldtype: "HTML", - fieldname: "payment_form", - options: ` - - ` - } - ] - }); + const iframe_html = ` + + `; - dialog.show(); + document.getElementById("iframe_container").innerHTML = iframe_html; } }); + + // Auto refresh every 5 seconds while dialog open + const poller = setInterval(() => { + frappe.call({ + method: "frappe.client.get", + args: { + doctype: "Sales Invoice", + name: frm.doc.name + }, + callback(r) { + if (r.message.outstanding_amount <= 0) { + clearInterval(poller); + dialog.hide(); + frm.reload_doc(); + frappe.msgprint("Payment successful!"); + } + } + }); + }, 5000); +} + + +function run_autopay(frm, autopay_id) { + + frappe.confirm( + `Run AutoPay for $${frm.doc.grand_total}?`, + () => { + + frm.disable_save(); + + frappe.call({ + method: "ns_app.api.payments.run_autopay_payment", + args: { + invoice: frm.doc.name, + autopay_id: autopay_id, + amount: frm.doc.grand_total + }, + callback(r) { + frappe.msgprint("Payment successful"); + frm.reload_doc(); + } + }); + } + ); }