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:
203
ns_app/api/payment_flow_documentation.md
Normal file
203
ns_app/api/payment_flow_documentation.md
Normal 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#
|
||||
Reference in New Issue
Block a user