Changed the opt in for auto pay to be a check box on thecustom form. Improves work flow and is more professional. Added this as checkbox under the zip code entry field. Did no testing on the logic yet.
This commit is contained in:
@@ -118,8 +118,10 @@ def call_payment_api(payload):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def run_token_payment(invoice, token, cardholder_name=None, billing_zip=None):
|
||||
def run_token_payment(invoice, token, cardholder_name=None, billing_zip=None, save_autopay=0):
|
||||
|
||||
inv = frappe.get_doc("Sales Invoice", invoice)
|
||||
customer = frappe.get_doc("Customer", inv.customer)
|
||||
|
||||
url = "https://secure.nmi.com/api/transact.php"
|
||||
|
||||
@@ -138,12 +140,13 @@ def run_token_payment(invoice, token, cardholder_name=None, billing_zip=None):
|
||||
or ""
|
||||
)
|
||||
|
||||
# Name Split
|
||||
# Name split
|
||||
parts = customer_name.strip().split(" ", 1)
|
||||
first_name = parts[0]
|
||||
last_name = parts[1] if len(parts) > 1 else "."
|
||||
|
||||
data = {
|
||||
|
||||
sale_data = {
|
||||
"security_key": frappe.conf.get("nmi_security_key"),
|
||||
"type": "sale",
|
||||
"payment_token": token,
|
||||
@@ -153,72 +156,110 @@ def run_token_payment(invoice, token, cardholder_name=None, billing_zip=None):
|
||||
"first_name": first_name,
|
||||
"last_name": last_name,
|
||||
"email": inv.contact_email or "",
|
||||
|
||||
"zip": billing_zip,
|
||||
}
|
||||
|
||||
response = requests.post(url, data=data)
|
||||
result = urllib.parse.parse_qs(response.text)
|
||||
sale_response = requests.post(url, data=sale_data)
|
||||
sale_result = urllib.parse.parse_qs(sale_response.text)
|
||||
|
||||
frappe.logger("payments").info(f"NMI RESPONSE: {response.text}")
|
||||
frappe.logger("payments").info(f"NMI SALE RESPONSE: {sale_response.text}")
|
||||
|
||||
success = result.get("response", ["0"])[0]
|
||||
transaction_id = result.get("transactionid", [""])[0]
|
||||
success = sale_result.get("response", ["0"])[0]
|
||||
transaction_id = sale_result.get("transactionid", [""])[0]
|
||||
|
||||
if success == "1":
|
||||
create_payment_entry(
|
||||
invoice=invoice,
|
||||
amount=inv.outstanding_amount,
|
||||
transaction_id=transaction_id,
|
||||
mode_of_payment="Credit Card"
|
||||
)
|
||||
if success != "1":
|
||||
return {
|
||||
"success": False,
|
||||
"error": sale_result.get("responsetext", ["Error"])[0]
|
||||
}
|
||||
|
||||
return {"success": True}
|
||||
# Create payment entry
|
||||
create_payment_entry(
|
||||
invoice=invoice,
|
||||
amount=inv.outstanding_amount,
|
||||
transaction_id=transaction_id,
|
||||
mode_of_payment="Credit Card"
|
||||
)
|
||||
|
||||
vault_id = None
|
||||
|
||||
# Vault (if checked)
|
||||
if cint(save_autopay):
|
||||
vault_data = {
|
||||
"security_key": frappe.conf.get("nmi_security_key"),
|
||||
"type": "add_customer",
|
||||
"payment_token": token,
|
||||
|
||||
"first_name": first_name,
|
||||
"last_name": last_name,
|
||||
"zip": billing_zip,
|
||||
|
||||
"customer_vault": "add_customer"
|
||||
}
|
||||
|
||||
try:
|
||||
vault_response = requests.post(url, data=vault_data, timeout=30)
|
||||
vault_result = urllib.parse.parse_qs(vault_response.text)
|
||||
|
||||
frappe.logger("payments").info(f"NMI VAULT RESPONSE: {vault_response.text}")
|
||||
|
||||
vault_success = vault_result.get("response", ["0"])[0]
|
||||
vault_id = vault_result.get("customer_vault_id", [""])[0]
|
||||
|
||||
if vault_success == "1" and vault_id:
|
||||
customer.custom_auto_pay_id = vault_id
|
||||
customer.custom_auto_pay_status = 1
|
||||
customer.save(ignore_permissions=True)
|
||||
|
||||
except Exception:
|
||||
frappe.log_error(frappe.get_traceback(), "Vault Creation Failed")
|
||||
|
||||
return {
|
||||
"success": False,
|
||||
"error": result.get("responsetext", ["Error"])[0]
|
||||
"success": True,
|
||||
"vault_id": vault_id
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
def save_to_autopay(customer, token, cardholder_name=None, billing_zip=None):
|
||||
import requests
|
||||
import urllib.parse
|
||||
|
||||
inv_customer = frappe.get_doc("Customer", customer)
|
||||
cust = frappe.get_doc("Customer", customer)
|
||||
|
||||
# Fallback logic
|
||||
customer_name = (
|
||||
# Priority: Form input → Customer record → fallback
|
||||
final_name = (
|
||||
cardholder_name
|
||||
or inv_customer.customer_name
|
||||
or cust.customer_name
|
||||
or cust.name
|
||||
or "Customer"
|
||||
).strip()
|
||||
|
||||
billing_zip = (
|
||||
# ZIP fallback chain
|
||||
final_zip = (
|
||||
billing_zip
|
||||
or inv_customer.get("billing_zip")
|
||||
or inv_customer.get("pincode")
|
||||
or cust.get("billing_zip")
|
||||
or cust.get("pincode")
|
||||
or ""
|
||||
)
|
||||
|
||||
# name split
|
||||
if " " in customer_name:
|
||||
first_name, last_name = customer_name.split(" ", 1)
|
||||
else:
|
||||
first_name = customer_name
|
||||
last_name = "."
|
||||
# Optional but recommended fields
|
||||
email = cust.get("email_id") or ""
|
||||
|
||||
# Safe name split
|
||||
parts = final_name.split(" ", 1)
|
||||
first_name = parts[0]
|
||||
last_name = parts[1] if len(parts) > 1 else "."
|
||||
|
||||
data = {
|
||||
"security_key": frappe.conf.get("nmi_security_key"),
|
||||
"type": "add_customer",
|
||||
|
||||
"payment_token": token,
|
||||
"customer_vault": "add_customer",
|
||||
|
||||
"first_name": first_name,
|
||||
"last_name": last_name,
|
||||
"email": email,
|
||||
|
||||
"zip": billing_zip,
|
||||
|
||||
"customer_vault": "add_customer"
|
||||
"zip": final_zip,
|
||||
}
|
||||
|
||||
try:
|
||||
@@ -230,7 +271,10 @@ def save_to_autopay(customer, token, cardholder_name=None, billing_zip=None):
|
||||
|
||||
result = urllib.parse.parse_qs(response.text)
|
||||
|
||||
frappe.logger("payments").info(f"NMI VAULT RESPONSE: {response.text}")
|
||||
frappe.logger("payments").info({
|
||||
"vault_request": data,
|
||||
"vault_response": response.text
|
||||
})
|
||||
|
||||
success = result.get("response", ["0"])[0]
|
||||
vault_id = result.get("customer_vault_id", [""])[0]
|
||||
@@ -241,14 +285,25 @@ def save_to_autopay(customer, token, cardholder_name=None, billing_zip=None):
|
||||
return {"success": False, "error": "Vault request failed"}
|
||||
|
||||
if success == "1" and vault_id:
|
||||
# Save to customer
|
||||
inv_customer.custom_auto_pay_id = vault_id
|
||||
inv_customer.custom_auto_pay_status = 1
|
||||
inv_customer.save(ignore_permissions=True)
|
||||
|
||||
return {"success": True}
|
||||
# Save vault ID to ERP Customer
|
||||
cust.custom_auto_pay_id = vault_id
|
||||
cust.custom_auto_pay_status = 1
|
||||
|
||||
return {"success": False, "error": message}
|
||||
cust.custom_auto_pay_name = final_name
|
||||
cust.custom_auto_pay_zip = final_zip
|
||||
|
||||
cust.save(ignore_permissions=True)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"vault_id": vault_id
|
||||
}
|
||||
|
||||
return {
|
||||
"success": False,
|
||||
"error": message
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
|
||||
@@ -141,6 +141,13 @@ function open_manual_payment_form(frm) {
|
||||
<input type="text" id="billing_zip_${uid}" class="form-control"/>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<label>
|
||||
<input type="checkbox" id="save_autopay_${uid}" />
|
||||
Save for Auto Pay
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="cc_number_${uid}" class="mt-3"></div>
|
||||
<div id="cc_exp_${uid}" class="mt-2"></div>
|
||||
<div id="cc_cvv_${uid}" class="mt-2"></div>
|
||||
@@ -254,15 +261,22 @@ function open_manual_payment_form(frm) {
|
||||
callback: function (response) {
|
||||
if (response.token) {
|
||||
|
||||
// Get name and ZIP
|
||||
const enteredName = document.getElementById(`cardholder_name_${uid}`)?.value;
|
||||
const enteredZip = document.getElementById(`billing_zip_${uid}`)?.value;
|
||||
|
||||
// Save name and ZIP
|
||||
const finalName = enteredName || frm.doc.customer_name;
|
||||
const finalZip = enteredZip || frm.doc.billing_zip || frm.doc.pincode;
|
||||
|
||||
// Check autopay enrollment choice
|
||||
const save_autopay = document.getElementById(`save_autopay_${uid}`)?.checked;
|
||||
|
||||
|
||||
run_token_payment(frm, response.token, dialog, {
|
||||
cardholder_name: finalName,
|
||||
billing_zip: finalZip
|
||||
billing_zip: finalZip,
|
||||
save_autopay: save_autopay
|
||||
});
|
||||
|
||||
} else {
|
||||
@@ -294,49 +308,32 @@ function open_manual_payment_form(frm) {
|
||||
|
||||
|
||||
function run_token_payment(frm, token, dialog, extra_data = {}) {
|
||||
|
||||
const save_autopay = extra_data.save_autopay ? 1 : 0;
|
||||
|
||||
frappe.call({
|
||||
method: "ns_app.api.payments.run_token_payment",
|
||||
args: {
|
||||
invoice: frm.doc.name,
|
||||
token: token,
|
||||
cardholder_name: extra_data.cardholder_name,
|
||||
billing_zip: extra_data.billing_zip
|
||||
billing_zip: extra_data.billing_zip,
|
||||
save_autopay: save_autopay
|
||||
},
|
||||
callback(r) {
|
||||
if (r.message?.success) {
|
||||
|
||||
frappe.confirm(
|
||||
"Payment successful. Save this card for AutoPay?",
|
||||
() => {
|
||||
// YES → save to vault
|
||||
frappe.call({
|
||||
method: "ns_app.api.payments.save_to_autopay",
|
||||
args: {
|
||||
customer: frm.doc.customer,
|
||||
token: token,
|
||||
cardholder_name: extra_data.cardholder_name,
|
||||
billing_zip: extra_data.billing_zip
|
||||
},
|
||||
callback(res) {
|
||||
if (res.message?.success) {
|
||||
frappe.show_alert({
|
||||
message: "AutoPay enabled",
|
||||
indicator: "green"
|
||||
});
|
||||
} else {
|
||||
frappe.msgprint(res.message?.error || "Failed to save AutoPay");
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
() => {
|
||||
// NO
|
||||
frappe.show_alert({
|
||||
message: "Payment completed (not saved)",
|
||||
indicator: "blue"
|
||||
});
|
||||
}
|
||||
);
|
||||
if (save_autopay && r.message.vault_id) {
|
||||
frappe.show_alert({
|
||||
message: "Payment successful + AutoPay enabled",
|
||||
indicator: "green"
|
||||
});
|
||||
} else {
|
||||
frappe.show_alert({
|
||||
message: "Payment successful",
|
||||
indicator: "green"
|
||||
});
|
||||
}
|
||||
|
||||
dialog.hide();
|
||||
frm.reload_doc();
|
||||
|
||||
Reference in New Issue
Block a user