Author: Ty Reynolds Description: Full flow of what happens now from the moment a user opens the payment form through vault creation and payment entry generation. 1. User Opens “Pay Invoice” Form Frontend JS runs: open_manual_payment_form(frm) This creates a custom Frappe dialog containing: Cardholder Name Billing ZIP Secure card fields from Collect.js “Subscribe to Auto Pay” checkbox Pay button At this point, no card data touches ERPNext, Collect.js owns the secure fields 2. Collect.js Initializes Secure Fields These divs:
are replaced by NMI-hosted iframe fields. This is important because card numbers never enter our JS, CVV never reaches ERPNext, PCI scope stays much lower and we don't have worry. 3. User Enters Card Info User types: card number expiration CVV ZIP cardholder name Optionally checks: Subscribe to Auto Pay 4. User Clicks “Pay” JS gathers "const save_autopay = checkbox.checked ? 1 : 0;". Then Collect.js tokenizes the card. Instead of returning card data, NMI returns "payment_token". This token represents the card securely. 5. Frontend Calls Backend API Frontend sends: frappe.call({ method: "ns_app.api.payments.run_token_payment", args: { invoice, token, cardholder_name, billing_zip, save_autopay } }) ERPNext now receives: token invoice checkbox value customer metadata But not the raw card data. 6. Backend Starts run_token_payment() Python method begins: run_token_payment(...) First debug logs run: AUTOPAY DEBUG - START This is to confirm invoice arrived and checkbox value arrived 7. Feature Flag Protection Runs This executes: if not frappe.conf.get("enable_autopay_signup"): save_autopay = 0 Meaning checkbox can exist but vaulting can be globally disabled Then "save_autopay = int(save_autopay or 0)" normalizes the value safely. 8. Invoice + Customer Are Loaded Backend loads: inv = frappe.get_doc("Sales Invoice", invoice) customer = frappe.get_doc("Customer", inv.customer) Now the system knows exact invoice, ERP customer and outstanding balance 9. Name + ZIP Are Prepared Fallback logic determines first_name, last_name and billing_zip This is used both for payment and vault creation 10. SALE Transaction Runs Backend sends: sale_data = { "type": "sale", "payment_token": token, ... } to: https://secure.nmi.com/api/transact.php NMI processes the payment. 11. NMI Returns Payment Response NMI returns query-string style data: response=1 transactionid=123456 Python then parses it. 12. Payment Success Check If response == "1", then payment succeeded. If not: Error returned to frontend, payment entry NOT created 13. ERPNext Payment Entry Is Created Code runs: create_payment_entry(...) This creates Payment Entry, allocates against invoice, submits it automatically and invoice balance updates immediately. 14. Vault Logic Begins (Only If Checked) This section runs ONLY if save_autopay == 1, which means the box is checked, and feature flag enabled. Debug log: AUTOPAY DEBUG - VAULT ENTRY 15. Vault Request Sent to NMI Backend sends customer_vault=add_customer and payment_token This tells NMI to store this payment method securely 16. NMI Creates Customer Vault Entry NMI returns response=1 customer_vault_id=****** This vault ID is the reusable payment profile. No card data is stored in ERPNext. Only the vault reference. 17. ERPNext Customer Is Updated If successful: customer.custom_auto_pay_id = vault_id and customer.custom_auto_pay_status = 1 Now the ERP Customer is marked as enrolled in autopay and linked to NMI vault profile 18. Autopay Transaction Flow Autopay requests send: "type": "sale", "customer_vault_id": vault_id NMI charges the saved method directly. 19. Webhook Safety If NMI later posts payment confirmations crystalclear_webhook() can also create payment entries automatically. Security Model: Our system is currently designed to -> Never have ERPNext store card numbers. CVV never touches our backend. Collect.js handles PCI-sensitive data. NMI stores vault securely. ERPNext only stores vault ID root@erpnext:/home/norman/frappe-bench#