import frappe import requests from frappe.utils import nowdate @frappe.whitelist() def check_autopay(customer): cust = frappe.get_doc("Customer", customer) return { "autopay_enabled": bool(cust.custom_auto_pay_status), "autopay_id": cust.auto_pay_id if cust.custom_auto_pay_status else None } @frappe.whitelist() def run_autopay_payment(invoice): inv = frappe.get_doc("Sales Invoice", invoice) if inv.outstanding_amount <= 0: frappe.throw("Invoice is already fully paid") cust = frappe.get_doc("Customer", inv.customer) if not cust.custom_auto_pay_status or not cust.auto_pay_id: frappe.throw("Customer does not have AutoPay enabled") payload = { "autopay_id": cust.auto_pay_id, "amount": float(inv.outstanding_amount), "invoice": inv.name } response = call_payment_api(payload) if not response.get("success"): frappe.throw(response.get("error", "Payment failed")) payment_entry = create_payment_entry( invoice=inv.name, amount=payload["amount"], transaction_id=response.get("transaction_id") ) return { "message": "AutoPay payment successful", "payment_entry": payment_entry } def call_payment_api(payload): url = "https://crystalclear.transactiongateway.com/api/transact.php" api_username = frappe.conf.get("nmi_username") api_password = frappe.conf.get("nmi_password") if not api_username or not api_password: frappe.throw("Payment gateway credentials not configured") data = { "username": api_username, "password": api_password, "type": "sale", "customer_vault_id": payload["autopay_id"], "amount": payload["amount"], "orderid": payload["invoice"], "response": "json" } try: response = requests.post(url, data=data, timeout=30) response.raise_for_status() result = response.json() except Exception: frappe.log_error(frappe.get_traceback(), "NMI Payment API Error") frappe.throw("Payment processor unreachable") if result.get("response") == "1": return { "success": True, "transaction_id": result.get("transactionid") } return { "success": False, "error": result.get("responsetext", "Payment failed") } @frappe.whitelist() def get_collect_checkout_url(invoice): inv = frappe.get_doc("Sales Invoice", invoice) return ( "https://crystalclear.transactiongateway.com/collect/checkout" f"?amount={inv.outstanding_amount}&reference={inv.name}" ) @frappe.whitelist(allow_guest=True) def crystalclear_webhook(): data = frappe.local.form_dict # Validate success if data.get("response") != "1": return "ignored" invoice = data.get("orderid") amount = data.get("amount") transaction_id = data.get("transactionid") # Prevent duplicates if frappe.db.exists("Payment Entry", {"reference_no": transaction_id}): return "duplicate" create_payment_entry( invoice=invoice, amount=amount, transaction_id=transaction_id ) return "ok" def create_payment_entry(invoice, amount, transaction_id=None): inv = frappe.get_doc("Sales Invoice", invoice) paid_to = frappe.db.get_value( "Company", inv.company, "default_cash_account" ) if not paid_to: frappe.throw("Default cash account not set for company") pe = frappe.new_doc("Payment Entry") pe.payment_type = "Receive" pe.party_type = "Customer" pe.party = inv.customer pe.posting_date = nowdate() pe.paid_amount = amount pe.received_amount = amount pe.paid_to = paid_to pe.reference_no = transaction_id pe.reference_date = nowdate() pe.append("references", { "reference_doctype": "Sales Invoice", "reference_name": invoice, "allocated_amount": amount }) pe.insert(ignore_permissions=True) pe.submit() return pe.name