Files
ns_erpnext_app/ns_app/api/payment_flow_documentation.md

4.2 KiB

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

  1. 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.

  1. User Enters Card Info

User types:

card number expiration CVV ZIP cardholder name

Optionally checks:

Subscribe to Auto Pay

  1. 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.

  1. 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.

  1. 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

  1. 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.

  1. 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

  1. Name + ZIP Are Prepared

Fallback logic determines first_name, last_name and billing_zip

This is used both for payment and vault creation

  1. SALE Transaction Runs

Backend sends:

sale_data = { "type": "sale", "payment_token": token, ... }

to:

https://secure.nmi.com/api/transact.php

NMI processes the payment.

  1. NMI Returns Payment Response

NMI returns query-string style data: response=1 transactionid=123456

Python then parses it.

  1. Payment Success Check

If response == "1", then payment succeeded.

If not:

Error returned to frontend, payment entry NOT created

  1. 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.

  1. 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

  1. Vault Request Sent to NMI

Backend sends customer_vault=add_customer and payment_token

This tells NMI to store this payment method securely

  1. 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.

  1. 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

  1. Autopay Transaction Flow

Autopay requests send:

"type": "sale", "customer_vault_id": vault_id

NMI charges the saved method directly.

  1. 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#