/** * Unified cashier: renders the gateway result returned by PaymentOrchestrator::pay(). * * mode=qrcode on-site QR modal (+ optional mobile deep link) * mode=redirect same-tab navigation to the gateway page * mode=form auto-submit the server-built POST form * mode=jsapi WeChat in-app WeixinJSBridge payment * * Polling stays with the caller (AksrtVipPurchaseCoordinator.poll); this module * only presents the payment step and reports when the QR modal is dismissed. * * @package AKSRT */ (function (window, document) { "use strict"; var KIND = "pay-cashier"; var sequence = 0; var listeners = Object.create(null); function escapeHtml(value) { return String(value == null ? "" : value).replace(/[&<>"']/g, function (ch) { return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[ch]; }); } function isMobile() { return /Android|iPhone|iPad|iPod|Mobile/i.test(window.navigator.userAgent || ""); } function qrDataUrl(text) { if (typeof window.qrcode !== "function" || !text) return ""; try { var qr = window.qrcode(0, "M"); qr.addData(String(text)); qr.make(); return qr.createDataURL(6, 12); } catch (error) { return ""; } } function amountText(data) { if (data.amount === undefined || data.amount === null || data.amount === "") return ""; var mark = (window._win && window._win.pay_mark) || "¥"; return mark + String(data.amount); } /** Inner QR markup; reusable by callers that own their own modal. */ function qrHtml(data) { var src = qrDataUrl(data.code_url); var html = '
'; var amount = amountText(data); if (amount) html += '
' + escapeHtml(amount) + "
"; html += src ? '支付二维码' : '

二维码组件未加载,请刷新页面重试。

'; html += '

' + escapeHtml(data.message || "请使用手机扫码完成支付") + "

"; if (data.payment_url && isMobile()) { html += '打开支付应用'; } if (data.order_number) { html += '

订单号:' + escapeHtml(data.order_number) + "

"; } return html + "
"; } function openQr(data, options) { var runtime = window.AksrtModalRuntime; if (!runtime) return false; var id = String(++sequence); if (typeof options.onClose === "function") listeners[id] = options.onClose; runtime.open({ kind: KIND, cashierId: id, title: options.title || "扫码支付", className: "aksrt-pay-cashier-modal", telemetry: false, html: qrHtml(data), }); return true; } function submitForm(html) { var host = document.createElement("div"); host.hidden = true; host.innerHTML = String(html || ""); var form = host.querySelector("form"); if (!form) return false; document.body.appendChild(host); form.submit(); return true; } function invokeJsapi(params, options) { var run = function () { window.WeixinJSBridge.invoke("getBrandWCPayRequest", params, function (res) { var msg = res && res.err_msg ? String(res.err_msg) : ""; if (msg === "get_brand_wcpay_request:ok") return; if (typeof options.onClose === "function") { options.onClose(msg === "get_brand_wcpay_request:cancel" ? "cancel" : "error"); } }); }; if (window.WeixinJSBridge && typeof window.WeixinJSBridge.invoke === "function") { run(); } else { document.addEventListener("WeixinJSBridgeReady", run, { once: true }); } return true; } /** * Present a pending payment. Returns the handled mode, or "" when the * payload carries no cashier step (synchronous gateways). * * options.onClose(reason) fires when the QR modal is dismissed or the * JSAPI sheet is cancelled; callers stop polling there. */ function handle(data, options) { data = data || {}; options = options || {}; switch (data.mode) { case "qrcode": return data.code_url && openQr(data, options) ? "qrcode" : ""; case "redirect": if (!data.payment_url) return ""; window.location.assign(data.payment_url); return "redirect"; case "form": return submitForm(data.form_html) ? "form" : ""; case "jsapi": return data.jsapi_params ? invokeJsapi(data.jsapi_params, options) && "jsapi" : ""; default: return ""; } } /** Close the cashier QR modal after a terminal poll result. */ function close() { var runtime = window.AksrtModalRuntime; var shell = runtime ? runtime.getActive() : null; if (shell && shell.classList.contains("aksrt-pay-cashier-modal")) runtime.close("paid"); } document.addEventListener("aksrt:modal:close", function (event) { var detail = event && event.detail ? event.detail : {}; var opts = detail.session && detail.session.options ? detail.session.options : {}; if (opts.kind !== KIND || !listeners[opts.cashierId]) return; var fn = listeners[opts.cashierId]; delete listeners[opts.cashierId]; if (detail.reason !== "paid") fn(detail.reason || "close"); }); window.AksrtPayCashier = { KIND: KIND, handle: handle, close: close, qrHtml: qrHtml, isPending: function (data) { return !!(data && data.mode); }, }; })(window, document);