Finished sign up for auto pay feature from pay invoice form. Also, added a doc file for the payment flow.

This commit is contained in:
Ty Reynolds
2026-05-15 13:47:35 -04:00
parent d45e7cbbe1
commit 8c4f1f753e
3 changed files with 1107 additions and 288 deletions

View File

@@ -0,0 +1,203 @@
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:
<div id="cc_number_x"></div>
<div id="cc_exp_x"></div>
<div id="cc_cvv_x"></div>
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#