/** * AKSRT VIP pay modal — JSON view model + canonical purchase coordinator. * * @package AKSRT */ (function () { "use strict"; function text(value) { return String(value == null ? "" : value); } function node(tag, className, value) { var el = document.createElement(tag); if (className) el.className = className; if (value !== undefined && value !== null) el.textContent = text(value); return el; } function lazyImage(key, src, className, size) { var template = document.createElement("template"); template.innerHTML = "'; return template.content.firstElementChild; } function append(parent, child) { if (child) parent.appendChild(child); return child; } function levelMap(levels) { var map = Object.create(null); (Array.isArray(levels) ? levels : []).forEach(function (level) { if (level && level.level) map[String(level.level)] = level; }); return map; } function actionLabel(action) { if (action === "renew") return "续费"; if (action === "upgrade") return "升级"; return "开通"; } function actionKey(action) { return action === "pay" ? "purchase" : action; } function actionReason(reason) { if (reason === "no_payment_method") return "暂无可用支付方式"; if (reason === "no_package") return "暂无可用套餐"; if (reason === "not_available") return "当前不可用"; return ""; } function renderEquity(parent, value) { if (!value) return; var equity = node("div"); // The server sanitizes and wraps this admin-authored fragment before JSON. equity.innerHTML = text(value); while (equity.firstChild) parent.appendChild(equity.firstChild); } function renderProducts(parent, items) { if (!Array.isArray(items) || !items.length) { append(parent, node("div", "muted-2-color text-center padding-10", "暂无可用套餐")); return; } var wrap = node("div", "mb10 label-box charge-box aksrt-vip-products"); items.forEach(function (item, index) { if (!item || !item.package_id) return; var label = node("label", "aksrt-vip-product-label"); var input = node("input"); input.className = "hide vip-product-input"; input.type = "radio"; input.name = "vip_product_id_" + String(item.level || "group"); input.setAttribute("data-aksrt-vip-package", ""); input.value = String(item.package_id); input.checked = index === 0; label.appendChild(input); var product = node("div", "zib-widget vip-product relative text-center product-box"); if (item.discount_tag) append(product, node("div", "abs-right vip-tag badg jb-yellow", item.discount_tag)); if (item.title) append(product, node("div", "em09 mb3", item.title)); var priceRow = node("div", "flex abl gap6 jc"); append(priceRow, node("div", "product-price c-red", item.price_label || "")); if (item.show_price_label) { var original = node("div", "original-price relative muted-2-color"); var strike = node("s", "", item.show_price_label); append(original, strike); append(priceRow, original); } append(product, priceRow); if (item.days_label) append(product, node("div", "muted-2-color c-blue em09 p2-10 badg radius", item.days_label)); append(label, product); append(wrap, label); }); append(parent, wrap); } function renderMethods(parent, methods) { methods = Array.isArray(methods) ? methods : []; if (!methods.length) { append(parent, node("div", "muted-2-color text-center padding-10", "暂无可用支付方式")); return; } if (methods.length === 1) { var one = node("input"); one.type = "hidden"; one.name = "aksrt_vip_pay_method"; one.value = text(methods[0].id || "balance"); one.setAttribute("data-aksrt-vip-method-fixed", ""); append(parent, one); return; } var wrap = node("div", "uc-vip-shop__methods mb10"); wrap.setAttribute("data-aksrt-vip-methods", ""); append(wrap, node("span", "uc-vip-shop__methods-label", "支付方式")); methods.forEach(function (method, index) { var label = node("label", "uc-vip-shop__method"); var input = node("input"); input.type = "radio"; input.name = "aksrt_vip_pay_method"; input.value = text(method.id || "balance"); input.checked = index === 0; append(label, input); append(label, node("span", "", method.name || method.id || "")); append(wrap, label); }); append(parent, wrap); } function renderPointsModal(body, view) { view = view || {}; if (body.replaceChildren) body.replaceChildren(); else body.innerHTML = ""; var catalog = view.catalog || {}; var exchange = catalog.points_exchange || {}; var member = view.member || {}; var packages = Array.isArray(exchange.packages) ? exchange.packages : []; var currency = text(catalog.currency_name || "积分"); var modal = node("div", "box-body payvip-modal aksrt-vip-points-modal"); modal.setAttribute("data-aksrt-vip-points-modal-body", ""); var close = node("button", "aksrt-modal-close", "×"); close.type = "button"; close.setAttribute("data-aksrt-modal-close", ""); close.setAttribute("aria-label", "关闭"); append(modal, close); append(modal, node("h2", "aksrt-vip-points-modal__title", "积分兑换会员")); append(modal, node("p", "aksrt-vip-points-modal__desc", exchange.desc || "使用积分兑换会员权益。")); var balance = node("div", "aksrt-vip-points-modal__balance"); append(balance, node("span", "aksrt-vip-points-modal__balance-label", "我的积分")); append(balance, node("strong", "aksrt-vip-points-modal__balance-value", text(member.points || 0) + " " + currency)); append(modal, balance); var list = node("div", "aksrt-vip-points-modal__packages"); if (!view.enabled || view.allowed_actions && view.allowed_actions.points_exchange === false || !exchange.enabled || !packages.length) { append(list, node("div", "aksrt-vip-points-modal__empty", "暂无可兑换的会员")); } else { packages.forEach(function (item, index) { if (!item || !item.package_id) return; var cost = Number(item.points || 0); var enough = Number(member.points || 0) >= cost; var label = node("label", "aksrt-vip-points-option" + (enough ? "" : " is-disabled")); var input = node("input"); input.type = "radio"; input.name = "aksrt_vip_points_package"; input.value = String(item.package_id); input.checked = index === 0 && enough; input.disabled = !enough; input.setAttribute("data-aksrt-vip-points-package", ""); append(label, input); var card = node("span", "aksrt-vip-points-option__card"); append(card, node("strong", "aksrt-vip-points-option__title", item.title || ("VIP" + text(item.level || "")))); append(card, node("span", "aksrt-vip-points-option__days", item.days_label || "会员权益")); var costLabel = enough ? text(item.price_label || (cost + " " + currency)) : "积分不足"; append(card, node("span", "aksrt-vip-points-option__cost", costLabel)); append(label, card); append(list, label); }); } append(modal, list); var submit = node("button", "aksrt-vip-points-submit", "立即兑换"); submit.type = "button"; submit.setAttribute("data-aksrt-vip-points-submit", ""); submit.disabled = !view.enabled || !exchange.enabled || (view.allowed_actions && view.allowed_actions.points_exchange === false) || !packages.some(function (item) { return item && Number(member.points || 0) >= Number(item.points || 0); }); append(modal, submit); if (exchange.desc) append(modal, node("div", "aksrt-vip-points-modal__note", exchange.desc)); append(body, modal); } function pointsPackage(root) { var checked = root.querySelector('[data-aksrt-vip-points-package]:checked'); return checked ? parseInt(checked.value, 10) : 0; } function exchangeFromModal(root, button) { var packageId = pointsPackage(root); if (!button || button.disabled) return; if (!packageId) { notyf("请选择兑换套餐", "danger"); return; } var coordinator = window.AksrtVipPurchaseCoordinator; if (!coordinator || typeof coordinator.exchange !== "function") { notyf("会员兑换暂不可用", "danger"); return; } button.disabled = true; button.classList.add("is-loading"); notyf("正在兑换会员…", "load", "", "aksrt-vip-points-toast"); coordinator.exchange({ packageId: packageId, tags: ["vip", "orders"] }).then(function (response) { button.classList.remove("is-loading"); if (response && response.ok) { var data = response.data || response.json || {}; notyf(data.message || "兑换成功,正在刷新…", "success", 5, "aksrt-vip-points-toast"); window.setTimeout(function () { window.location.reload(); }, 800); return; } button.disabled = false; notyf(response && response.json && response.json.message ? response.json.message : "兑换失败,请重试。", "danger", 5, "aksrt-vip-points-toast"); }).catch(function () { button.disabled = false; button.classList.remove("is-loading"); notyf("网络异常,请稍后重试。", "danger", 5, "aksrt-vip-points-toast"); }); } function renderModalView(body, view) { view = view || {}; if (body.replaceChildren) body.replaceChildren(); else body.innerHTML = ""; if (!view.enabled) { append(body, node("div", "box-body payvip-modal text-center padding-20", "会员功能未启用。")); return; } var catalog = view.catalog || {}; var member = view.member || {}; var levels = levelMap(view.levels); var groups = Array.isArray(catalog.level_packages) ? catalog.level_packages : []; var methods = Array.isArray(view.payment_methods) ? view.payment_methods : []; var allowed = view.allowed_actions || {}; var reasons = view.action_reasons || {}; var modal = node("div", "box-body payvip-modal"); modal.setAttribute("data-aksrt-vip-modal", ""); if (view.version && view.version.catalog) modal.setAttribute("data-aksrt-vip-version", view.version.catalog); var close = node("button", "aksrt-modal-close", "×"); close.type = "button"; close.setAttribute("data-aksrt-modal-close", ""); close.setAttribute("aria-label", "关闭"); append(modal, close); var userBox = node("ul", "list-inline user-box flex ac mb10"); var avatarItem = node("li", "avatar-img"); if (member.avatar_url) { var avatar = node("img"); avatar.src = text(member.avatar_url); avatar.alt = ""; avatar.width = 42; avatar.height = 42; append(avatarItem, avatar); } append(userBox, avatarItem); var userInfo = node("li"); append(userInfo, node("b", "em12", member.name || "")); if (member.level > 0) { var expiry = node("div", "muted-2-color em09"); var badge = node("span", "badg c-yellow vip-expdate-tag", member.expires_label || ""); append(expiry, badge); append(userInfo, expiry); } else if (member.state === "expired") { append(userInfo, node("div", "muted-2-color em09", "已过期")); } else if (catalog.desc) { append(userInfo, node("div", "muted-2-color em09", catalog.desc)); } append(userBox, userInfo); append(modal, userBox); var tabs = node("ul", "flex jsb mt10 mb20 vip-cardminis gutters-5 nav"); var content = node("div", "tab-content mt10"); var activeIndex = -1; groups.forEach(function (group, index) { if (activeIndex >= 0 || !group) return; if (Number(group.level) === Number(view.prefer_level)) activeIndex = index; }); if (activeIndex < 0 && groups.length) activeIndex = 0; groups.forEach(function (group, index) { if (!group || !Array.isArray(group.items) || !group.items.length) return; var level = Number(group.level || 0); var action = text(group.action || "pay"); var tabId = "tab-payvip-" + level + "-" + action; var active = index === activeIndex; var tabItem = node("li", "relative" + (active ? " active" : "")); var tab = node("a", "aksrt-vip-tab", actionLabel(action) + (group.name || "VIP" + level)); tab.href = "javascript:;"; tab.setAttribute("data-vip-tab", tabId); append(tabItem, tab); append(tabs, tabItem); var pane = node("div", "tab-pane aksrt-vip-pane" + (active ? " active" : "")); pane.id = tabId; pane.hidden = !active; var row = node("div", "pay-vip-form-row"); var info = node("div", "pay-vip-info level-" + level); var levelInfo = levels[String(level)] || {}; var levelHeader = node("div", "aksrt-vip-level-header"); if (levelInfo.img_icon) { var levelIcon = lazyImage("lazy_other", text(levelInfo.img_icon), "aksrt-vip-level-icon"); append(levelHeader, levelIcon); } append(levelHeader, node("div", "v-name font-bold", group.name || levelInfo.name || "VIP" + level)); append(info, levelHeader); if (levelInfo.desc) append(info, node("div", "muted-2-color em09 mb6", levelInfo.desc)); renderEquity(info, levelInfo.equity); append(row, info); var products = node("div", "pay-vip-products"); renderProducts(products, group.items); renderMethods(products, methods); var actionName = actionKey(action); var actionAllowed = allowed[actionName] === true; var reason = actionReason(reasons[actionName]); var canPurchase = actionAllowed && methods.length > 0; var submit = node("button", "but jb-red btn-block padding-lg radius aksrt-vip-submit", canPurchase ? (group.items[0].cta_label || actionLabel(action)) : (reason || "暂无可用支付方式")); submit.type = "button"; submit.disabled = !canPurchase; submit.setAttribute("data-vip-submit", ""); append(products, submit); append(row, products); append(pane, row); append(content, pane); }); append(modal, tabs); append(modal, content); if (catalog.help_text) append(modal, node("div", "muted-2-color em09 mb6", catalog.help_text)); if (catalog.more) append(modal, node("div", "muted-2-color mb10 em09 muted-box padding-10 mini-radius", catalog.more)); var points = catalog.points_exchange || {}; if (allowed.points_exchange !== false && points.enabled && Array.isArray(points.packages) && points.packages.length && catalog.vip_url) { var pointsWrap = node("div", "mt10"); var pointsLink = node("a", "but c-green btn-block", "积分兑换会员"); pointsLink.href = text(catalog.vip_url); append(pointsWrap, pointsLink); append(modal, pointsWrap); } append(body, modal); } function selectedMethod(root) { var fixed = root.querySelector("[data-aksrt-vip-method-fixed]"); if (fixed && fixed.value) return fixed.value; var checked = root.querySelector('input[name="aksrt_vip_pay_method"]:checked'); if (checked) return checked.value; var first = root.querySelector('input[name="aksrt_vip_pay_method"]'); return first ? first.value : "balance"; } function selectedPackage(root) { var checked = root.querySelector('[data-aksrt-vip-package]:checked'); return checked ? parseInt(checked.value, 10) : 0; } var activePurchase = null; var activePurchaseToastId = ""; function restoreButton(button) { if (!button) return; button.disabled = false; button.classList.remove("is-loading"); } function purchaseFromPane(pane, button) { var packageId = selectedPackage(pane); if (!packageId) { notyf("请选择套餐", "danger"); return; } if (!button || button.disabled) return; var coordinator = window.AksrtVipPurchaseCoordinator; if (!coordinator || typeof coordinator.start !== "function") { notyf("会员购买暂不可用", "danger"); return; } var waitingPayment = false; button.disabled = true; button.classList.add("is-loading"); activePurchaseToastId = "aksrt-vip-purchase-toast"; notyf("正在创建会员订单…", "load", "", activePurchaseToastId); activePurchase = coordinator.start({ packageId: packageId, paymentMethod: selectedMethod(pane), tags: ["vip", "orders"], onPending: function (data) { waitingPayment = true; if (data.payment_url) window.open(data.payment_url, "_blank"); notyf(data.message || "请完成支付,支付成功后将自动刷新。", "info", 5, activePurchaseToastId); activePurchaseToastId = "aksrt-vip-payment-toast"; notyf("等待支付结果…", "load", "", activePurchaseToastId); }, onSuccess: function (result) { activePurchase = null; restoreButton(button); var toastId = waitingPayment ? "aksrt-vip-payment-toast" : "aksrt-vip-purchase-toast"; notyf((result.data && result.data.message) || "开通成功,正在刷新…", "success", 5, toastId); activePurchaseToastId = ""; window.setTimeout(function () { window.location.reload(); }, 800); }, onFailure: function (result) { activePurchase = null; restoreButton(button); notyf(result.data && result.data.message ? result.data.message : "支付未完成,请重试。", "danger", 5, "aksrt-vip-payment-toast"); activePurchaseToastId = ""; }, onTimeout: function () { activePurchase = null; restoreButton(button); notyf("支付状态暂未确认,请稍后在订单中心查看。", "danger", 5, "aksrt-vip-payment-toast"); activePurchaseToastId = ""; }, onError: function (response) { activePurchase = null; restoreButton(button); var message = response && response.json && response.json.message ? response.json.message : "开通失败"; if (response && response.status === 401) { message = "请先登录"; var login = document.querySelector(".signin-loader"); if (login) login.click(); } notyf(message, "danger", 5, activePurchaseToastId || "aksrt-vip-purchase-toast"); activePurchaseToastId = ""; }, }); } function bindModalUi(root) { if (!root || root.dataset.aksrtVipBound === "1") return; root.dataset.aksrtVipBound = "1"; root.addEventListener("click", function (event) { var tab = event.target.closest("[data-vip-tab]"); if (tab) { event.preventDefault(); var id = tab.getAttribute("data-vip-tab"); root.querySelectorAll(".aksrt-vip-tab").forEach(function (item) { if (item.parentElement) item.parentElement.classList.toggle("active", item === tab); }); root.querySelectorAll(".aksrt-vip-pane").forEach(function (pane) { var active = pane.id === id; pane.hidden = !active; pane.classList.toggle("active", active); }); return; } var pointsSubmit = event.target.closest("[data-aksrt-vip-points-submit]"); if (pointsSubmit) { event.preventDefault(); exchangeFromModal(pointsSubmit.closest("[data-aksrt-vip-points-modal-body]") || root, pointsSubmit); return; } var submit = event.target.closest("[data-vip-submit]"); if (submit) { event.preventDefault(); purchaseFromPane(submit.closest(".aksrt-vip-pane") || root, submit); } }); } function vipPointsPay() { if (!window.AksrtModalRuntime.isRolloutEnabled("vip")) { notyf("会员兑换暂不可用", "danger"); return; } window.AksrtModalRuntime.openRemote({ url: "/pay/vip/modal", query: { vip_level: 1, mode: "points" }, className: "aksrt-vip-modal-shell aksrt-vip-points-modal-shell", mobileBottom: false, skeleton: '
加载中…
', telemetry: "vip-points", kind: "vip", responseType: "json", cacheTtl: 15000, cacheTags: ["vip"], renderData: function (data, session) { renderPointsModal(session.content, data); bindModalUi(session.content); }, onError: function (error) { var unauthorized = error && error.code === "unauthorized"; notyf(unauthorized ? "请先登录" : ((error && error.message) || "会员兑换信息加载失败,请重试"), "danger"); if (unauthorized) { window.AksrtModalRuntime.close("unauthorized"); var login = document.querySelector(".signin-loader"); if (login) login.click(); } }, }); } function vipPay(trigger) { if (!window.AksrtModalRuntime.isRolloutEnabled("vip")) { notyf("会员购买暂不可用", "danger"); return; } var level = trigger ? parseInt(trigger.getAttribute("vip-level") || trigger.getAttribute("data-vip-level") || "1", 10) || 1 : 1; window.AksrtModalRuntime.openRemote({ url: "/pay/vip/modal", query: { vip_level: level }, className: "aksrt-vip-modal-shell", mobileBottom: false, skeleton: '
加载中…
', telemetry: "vip", kind: "vip", responseType: "json", cacheTtl: 15000, cacheTags: ["vip"], renderData: function (data, session) { renderModalView(session.content, data); bindModalUi(session.content); }, onError: function (error) { var unauthorized = error && error.code === "unauthorized"; notyf(unauthorized ? "请先登录" : ((error && error.message) || "会员套餐加载失败,请重试"), "danger"); if (unauthorized) { window.AksrtModalRuntime.close("unauthorized"); var login = document.querySelector(".signin-loader"); if (login) login.click(); } }, }); } function onDocClick(event) { var pointsTrigger = event.target.closest("[data-aksrt-vip-points-modal], [data-aksrt-vip-points-open]"); if (pointsTrigger) { event.preventDefault(); event.stopPropagation(); vipPointsPay(); return; } var trigger = event.target.closest(".pay-vip, a.pay-vip, .float-btn.pay-vip, [data-aksrt-pay-vip]"); if (!trigger) return; var href = (trigger.getAttribute("href") || "").trim(); if (href && href !== "#" && href.indexOf("javascript:") !== 0 && !trigger.classList.contains("pay-vip") && !trigger.hasAttribute("data-aksrt-pay-vip") && !trigger.hasAttribute("vip-level") && !trigger.hasAttribute("data-vip-level")) { return; } event.preventDefault(); event.stopPropagation(); vipPay(trigger); } document.addEventListener("aksrt:modal:close", function (event) { var detail = event && event.detail ? event.detail : {}; var options = detail.session && detail.session.options ? detail.session.options : {}; if (options.kind !== "vip" || !activePurchase) return; if (typeof activePurchase.abort === "function") activePurchase.abort(); activePurchase = null; if (activePurchaseToastId) { notyf("已取消会员购买", "warning", 3, activePurchaseToastId); activePurchaseToastId = ""; } }); function bindOnce() { if (window.__aksrtPayVipBound) return; window.__aksrtPayVipBound = true; document.addEventListener("click", onDocClick, true); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", bindOnce, { once: true }); } else { bindOnce(); } })();