frappe.ui.form.on("Sales Invoice", { refresh(frm) { frm.clear_custom_buttons(); // Only on submitted invoices if (frm.doc.docstatus !== 1) return; if (!frm.doc.customer) return; // Already paid if (frm.doc.outstanding_amount <= 0) { frm.dashboard.add_indicator("Paid", "green"); return; } frm.dashboard.add_indicator("Unpaid", "red"); if (frm.doc.outstanding_amount > 0 && frm.doc.docstatus === 1) { 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 }, callback(r) { if (!r.message) { frm.enable_save(); return; } if (r.message.autopay_enabled && r.message.autopay_id) { run_autopay(frm); } else { open_manual_payment_form(frm); } } }); } function run_autopay(frm) { frappe.confirm( `Run AutoPay for ${format_currency(frm.doc.outstanding_amount)}?`, () => { // Change button to processing frm.remove_custom_button("Run Payment"); frm.add_custom_button("Processing...", () => {}, null).prop("disabled", true); frappe.call({ method: "ns_app.api.payments.run_autopay_payment", args: { invoice: frm.doc.name }, freeze: true, freeze_message: "Processing payment...", callback(r) { if (!r.message) { show_payment_failed(frm, "No response from payment processor"); return; } if (r.message.success) { // Success UI frm.remove_custom_button("Run Payment"); frm.add_custom_button("Paid ✓", () => {}) .prop("disabled", true); frappe.show_alert({ message: `Payment of ${format_currency(frm.doc.outstanding_amount)} received`, indicator: "green" }); frm.reload_doc(); } else { show_payment_failed(frm, r.message.error || "Payment declined"); } } }); }, () => {} ); } function show_payment_failed(frm, message) { // Remove processing button frm.remove_custom_button("Processing..."); // Add retry button frm.add_custom_button("Retry Payment", () => { run_payment_flow(frm); }); frappe.msgprint({ title: "Payment Failed", indicator: "red", message: message }); } function open_manual_payment_form(frm) { const dialog = new frappe.ui.Dialog({ title: "Secure Payment", size: "large", fields: [ { fieldtype: "HTML", fieldname: "payment_form", options: `