Files
wordpress-theme-aksrt/assets/js/pay-vip.js
T
kate522 2801a89c1f fix(vip): 在 /user/vip/ 档位购买卡下方补充积分兑换会员入口
参考主题 zibpay_user_page_tab_content_vip:非会员 + 积分兑换会员开启 +
有可兑换套餐时,在两张档位购买卡下方输出全宽入口横条(points-color 图标
+ 标题 + 立即兑换 badg),点击经 data-aksrt-vip-points-open 打开 wp_footer
静态兑换弹窗,与会员购买弹窗内入口共用同一弹窗。

- aksrt-vip.php 新增 aksrt_get_vip_points_exchange_entry_html(),门控与
  弹窗壳同源(取服务端 catalog),已是会员不展示(对齐参考主题契约)
- vip-tab.php 在卡片区之后输出入口;credits.css 补 .uc-vip-points-entry
  表面样式(与档位卡同一变量体系,暗色自适应)
- pay-vip.js 改文档级委托承接 data-aksrt-vip-points-open,一处绑定覆盖
  静态页面横条与弹窗内按钮;移除 bindModalUi 内的重复分支
- 契约测试更新为双入口(11 项),node --test 420 项通过,php -l 通过

未写数据迁移;未做真实视觉效果验收。
2026-09-23 14:30:54 +08:00

782 lines
33 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 imageNode(src, className) {
if (!src) return null;
var image = document.createElement("img");
image.className = className || "";
image.src = text(src);
image.alt = "";
image.decoding = "async";
return image;
}
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_package") return "暂无可用套餐";
if (reason === "not_available") return "当前不可用";
return "";
}
// 会员图标(Zibll zibpay_get_vip_icon:vip_N.svg 或自定义 img_icon)。
function vipIcon(levelInfo) {
if (levelInfo && levelInfo.img_icon) {
var img = node("img", "img-icon");
img.src = text(levelInfo.img_icon);
img.alt = text(levelInfo.name || "");
img.decoding = "async";
return img;
}
return node("i", "fa fas fa-gem");
}
// 价格行(Zibll:mark 小号 px12 前缀 + 数字;原价 original-price.relative)。
function priceRow(mark, priceLabel, showLabel) {
var row = node("div", "flex abl gap6");
var price = node("div", "product-price c-red");
var label = text(priceLabel || "");
if (mark && label.indexOf(mark) === 0) {
append(price, node("span", "px12", mark));
append(price, node("span", "", label.slice(mark.length)));
} else {
append(price, node("span", "", label));
}
append(row, price);
if (showLabel) {
var original = node("div", "original-price relative muted-2-color");
var showText = text(showLabel);
if (mark && showText.indexOf(mark) === 0) {
append(original, node("span", "px12", mark));
append(original, node("span", "", showText.slice(mark.length)));
} else {
append(original, node("span", "", showText));
}
append(row, original);
}
return row;
}
function renderProducts(parent, items, mark) {
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");
items.forEach(function (item, index) {
if (!item || !item.package_id) return;
var label = node("label");
var input = node("input");
input.className = "hide vip-product-input";
input.type = "radio";
input.name = "vip_product_id";
input.value = String(item.package_id);
input.setAttribute("data-aksrt-vip-package", "");
input.checked = index === 0;
append(label, input);
// Zibll 套餐卡:zib-widget vip-product relative text-center product-box
// (右上 tag + 价格行 + 时长 badge,无标题行)。
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));
append(product, priceRow(mark, item.price_label || "", item.show_price_label || ""));
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, cardPass, wallet) {
methods = Array.isArray(methods) ? methods : [];
if (!methods.length) return; // 无可用支付方式:由按钮区渲染 Zibll 同款 badg 提示。
wallet = wallet || {};
var defaultMethod = text(methods[0].id || "balance");
// 支付方式选择行(Zibll zibpay_get_initiate_pay_input 同构:
// .payment-method-radio.hollow-radio + logo 图 + data-for 选中模型;
// 与余额充值/购买积分弹窗完全一致的组件)。
if (methods.length > 1) {
append(parent, node("div", "muted-2-color em09 mb6", "请选择支付方式"));
var row = node("div", "flex mb10");
methods.forEach(function (method, index) {
var id = text(method.id || "balance");
var item = node("div", "flex jc hh payment-method-radio hollow-radio flex-auto pointer" + (index === 0 ? " active" : ""));
item.setAttribute("data-for", "payment_method");
item.setAttribute("data-value", id);
if (method.logo) {
var img = node("img");
img.src = text(method.logo);
img.alt = id + "-logo";
img.decoding = "async";
append(item, img);
}
append(item, node("div", "", method.name || id || ""));
append(row, item);
});
append(parent, row);
}
// 我的余额盒(Zibll user_balance_box 同构:选中余额渠道时显示)。
if (methods.some(function (m) { return text(m.id) === "balance"; })) {
var balanceBox = node("div", "mb10 muted-box padding-h10");
balanceBox.setAttribute("data-controller", "payment_method");
balanceBox.setAttribute("data-condition", "==");
balanceBox.setAttribute("data-value", "balance");
if (defaultMethod !== "balance") balanceBox.style.display = "none";
var balanceRow = node("div", "flex jsb ac");
append(balanceRow, node("span", "muted-2-color", "我的余额"));
var balanceValue = node("div", "c-blue-2");
append(balanceValue, node("span", "px12 mr3", wallet.mark || "¥"));
append(balanceValue, node("span", "em14", text(wallet.balance || "0.00")));
append(balanceRow, balanceValue);
append(balanceBox, balanceRow);
append(parent, balanceBox);
}
// 卡密盒(Zibll password_box 同构:mb10 padding-h10 padding-w6 +
// data-controller="payment_method" 契约 + form-control 输入)。
if (cardPass) {
var box = node("div", "mb10 padding-h10 padding-w6");
box.setAttribute("data-controller", "payment_method");
box.setAttribute("data-condition", "==");
box.setAttribute("data-value", "card_pass");
if (defaultMethod !== "card_pass") box.style.display = "none";
if (cardPass.desc) append(box, node("div", "muted-box muted-2-color padding-10 mb10 em09", cardPass.desc));
append(box, node("div", "muted-2-color em09 mb6", cardPass.only_password ? "请输入卡密" : "请输入卡号和密码"));
if (!cardPass.only_password) {
var cardInput = node("input", "form-control mb6");
cardInput.type = "text";
cardInput.name = "card_pass[card]";
cardInput.placeholder = "卡号";
cardInput.autocomplete = "off";
append(box, cardInput);
}
var pwInput = node("input", "form-control");
pwInput.type = "text";
pwInput.name = "card_pass[password]";
pwInput.placeholder = "卡密";
pwInput.autocomplete = "off";
append(box, pwInput);
append(parent, box);
}
var hidden = node("input");
hidden.type = "hidden";
hidden.name = "payment_method";
hidden.value = defaultMethod;
append(parent, hidden);
}
// 渠道显隐:通用 data-controller 契约(==/!= 匹配当前 payment_method);
// 选中 card_pass 时隐藏套餐(.charge-box),与参考主题 pay.js 行为一致。
function applyVipMethodVisibility(root) {
var pane = root.closest ? root.closest(".aksrt-vip-pane") : null;
pane = pane || root;
var value = selectedMethod(pane);
pane.querySelectorAll('[data-controller="payment_method"]').forEach(function (el) {
var condition = el.getAttribute("data-condition") || "==";
var target = el.getAttribute("data-value") || "";
var match = condition === "!=" ? value !== target : value === target;
el.style.display = match ? "" : "none";
});
pane.querySelectorAll(".charge-box").forEach(function (el) {
el.style.display = value === "card_pass" ? "none" : "";
});
}
// ===== 积分兑换会员弹窗(参考主题 refresh_modal 同构静态壳) =====
// 弹窗壳由服务端静态输出(inc/helpers/aksrt-vip.php → wp_footer),
// 此处只负责开合与提交,形态对齐余额充值/购买积分弹窗的自包含开关:
// .in 显隐 + .modal-backdrop + body.modal-open + ESC,不依赖 jQuery/Bootstrap
// modal 运行时。
var pointsBackdrop = null;
var pointsBound = false;
var pointsFocusReturn = null;
function pointsModal() {
return document.querySelector("[data-aksrt-vip-points-modal]");
}
function openPointsModal(trigger) {
var modal = pointsModal();
if (!modal || modal.classList.contains("in")) return;
bindPointsModal();
pointsFocusReturn = trigger || document.activeElement;
// 打开态用 flex 而非 block:壳的 .flex.jc 负责双轴居中
// (.modal.flex .modal-dialog{margin:0} 在 block 下会把对话框顶到左上角)。
modal.style.display = "flex";
void modal.offsetWidth; // 强制 reflow,让 .fade 过渡从初始态开始
modal.classList.add("in");
if (!pointsBackdrop) {
pointsBackdrop = document.createElement("div");
pointsBackdrop.className = "modal-backdrop fade in";
pointsBackdrop.setAttribute("data-aksrt-vip-points-backdrop", "1");
document.body.appendChild(pointsBackdrop);
}
document.body.classList.add("modal-open");
// 焦点移入兑换弹窗:ESC 只收起本层,不会冒泡到下层会员弹窗。
var closeBtn = modal.querySelector('[data-dismiss="modal"]');
try { (closeBtn || modal).focus(); } catch (error) {}
}
function closePointsModal() {
var modal = pointsModal();
if (!modal || !modal.classList.contains("in")) return;
modal.classList.remove("in");
// 同页可能另有静态弹窗(余额充值/购买积分)打开,仅在没有 .modal.in 时解锁。
if (!document.querySelector(".modal.in")) document.body.classList.remove("modal-open");
if (pointsBackdrop && pointsBackdrop.parentNode) pointsBackdrop.parentNode.removeChild(pointsBackdrop);
pointsBackdrop = null;
window.setTimeout(function () {
if (modal && !modal.classList.contains("in")) modal.style.display = "";
var target = pointsFocusReturn;
pointsFocusReturn = null;
if (target && document.contains(target) && typeof target.focus === "function") {
try { target.focus(); } catch (error) {}
}
}, 300);
}
function exchangePoints(button) {
var modal = pointsModal();
if (!modal || !button || button.disabled) return;
var hidden = modal.querySelector('input[name="product_id"]');
var packageId = hidden ? parseInt(hidden.value, 10) || 0 : 0;
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.disabled = false;
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;
}
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 bindPointsModal() {
var modal = pointsModal();
if (!modal || pointsBound) return;
pointsBound = true;
modal.addEventListener("click", function (event) {
// 参考主题契约:点遮罩(壳自身)或 data-dismiss="modal" 关闭按钮收起弹窗。
if (event.target === modal || (event.target.closest && event.target.closest('[data-dismiss="modal"]'))) {
closePointsModal();
return;
}
// 参考主题 data-for/data-value 选中模型:会员兑换卡片。
var card = event.target.closest ? event.target.closest('[data-for="product_id"]') : null;
if (!card) return;
modal.querySelectorAll('[data-for="product_id"]').forEach(function (el) {
el.classList.toggle("active", el === card);
});
var hidden = modal.querySelector('input[name="product_id"]');
if (hidden) hidden.value = card.getAttribute("data-value") || "";
});
var form = modal.querySelector("[data-aksrt-vip-points-form]");
if (form) {
form.addEventListener("submit", function (event) {
event.preventDefault();
exchangePoints(modal.querySelector("[data-aksrt-vip-points-submit]"));
});
}
document.addEventListener("keydown", function (event) {
if (event.key === "Escape" && modal.classList.contains("in")) closePointsModal();
});
}
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 cardPass = view.card_pass && view.card_pass.enabled ? view.card_pass : null;
var wallet = { balance: text(catalog.wallet_balance || "0.00"), mark: text(view.currency_mark || "¥") };
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);
// 关闭按钮(Zibll 原版内联 SVG close 图标)。
var close = document.createElement("button");
close.type = "button";
close.className = "close";
close.setAttribute("data-aksrt-modal-close", "");
close.setAttribute("aria-hidden", "true");
close.setAttribute("aria-label", "关闭");
var closeSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
closeSvg.setAttribute("class", "ic-close");
closeSvg.setAttribute("viewBox", "0 0 1024 1024");
var closePath = document.createElementNS("http://www.w3.org/2000/svg", "path");
closePath.setAttribute("d", "M573.44 512.128l237.888 237.696a43.328 43.328 0 0 1 0 59.712 43.392 43.392 0 0 1-59.712 0L513.728 571.84 265.856 819.712a44.672 44.672 0 0 1-61.568 0 44.672 44.672 0 0 1 0-61.568L452.16 510.272 214.208 272.448a43.328 43.328 0 0 1 0-59.648 43.392 43.392 0 0 1 59.712 0l237.952 237.76 246.272-246.272a44.672 44.672 0 0 1 61.568 0 44.672 44.672 0 0 1 0 61.568L573.44 512.128z");
closeSvg.appendChild(closePath);
close.appendChild(closeSvg);
append(modal, close);
// 用户信息(Zibll:list-inline user-box)。
var userBox = node("ul", "list-inline user-box");
var avatarItem = node("li");
var avatarWrap = node("div", "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(avatarWrap, avatar);
}
append(avatarItem, avatarWrap);
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);
// 会员缩略卡 tabs(Zibll vip-cardminis:vip-card.vip-cardmini + active-icon 勾)。
var tabs = node("ul", "flex jsb mt10 mb20 vip-cardminis gutters-5");
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;
var active = index === activeIndex;
var levelInfo = levels[String(level)] || {};
var tabItem = node("li", "relative" + (active ? " active" : ""));
var tab = document.createElement("a");
tab.href = "javascript:;";
tab.setAttribute("data-vip-tab", tabId);
var miniCard = node("div", "vip-card vip-cardmini level-" + level + " vip-theme" + level);
var miniBa = node("div", "abs-center vip-baicon");
append(miniBa, vipIcon(levelInfo));
append(miniCard, miniBa);
var miniIconbox = node("div", "v-iconbox flex ac gap10");
var miniIcon = node("div", "v-icon flex jc");
append(miniIcon, vipIcon(levelInfo));
append(miniIconbox, miniIcon);
var miniInfo = node("div", "v-info");
append(miniInfo, node("div", "v-name", actionLabel(action) + (levelInfo.name || group.name || "VIP" + level)));
append(miniInfo, node("div", "v-desc em09", levelInfo.desc || ""));
append(miniIconbox, miniInfo);
append(miniCard, miniIconbox);
append(tab, miniCard);
append(tabItem, tab);
var activeIcon = node("div", "abs-right active-icon");
append(activeIcon, node("i", "fa fas fa-check-circle"));
activeIcon.setAttribute("aria-hidden", "true");
append(tabItem, activeIcon);
append(tabs, tabItem);
// pane(Zibll tab-pane fade active in)。
var pane = node("div", "tab-pane fade aksrt-vip-pane" + (active ? " active in" : ""));
pane.id = tabId;
pane.hidden = !active;
var form = document.createElement("form");
form.setAttribute("onsubmit", "return false;");
var row = node("div", "pay-vip-form-row");
// 左侧会员信息(Zibll zibpay_get_pay_vip_info_box)。
var info = node("div", "text-center pay-vip-info");
var iconbox = node("div", "v-iconbox mb10 flex jc gap10");
var vicon = node("div", "v-icon flex jc");
append(vicon, vipIcon(levelInfo));
append(iconbox, vicon);
var vinfo = node("div", "v-info");
append(vinfo, node("div", "v-name", levelInfo.name || group.name || "VIP" + level));
append(vinfo, node("div", "v-desc em09", levelInfo.desc || ""));
append(iconbox, vinfo);
append(info, iconbox);
if (levelInfo.equity) {
var equity = document.createElement("ul");
equity.className = "v-equity";
equity.innerHTML = text(levelInfo.equity);
append(info, equity);
}
var infoBa = node("div", "abs-center vip-baicon");
append(infoBa, vipIcon(levelInfo));
append(info, infoBa);
append(row, info);
// 右侧:套餐盒 → 积分兑换入口 → more → 支付方式/卡密 → 立即支付。
var products = node("div", "pay-vip-products");
renderProducts(products, group.items, wallet.mark);
var points = catalog.points_exchange || {};
if (allowed.points_exchange !== false && points.enabled && Array.isArray(points.packages) && points.packages.length) {
// 参考主题 zibpay_get_pay_uservip_modal 的积分兑换入口:
// btn-block but mb10 padding-lg c-green mini-radius + points-color 图标 +
// fa-angle-right;点击开服务端静态兑换弹窗(不再跳转用户中心)。
var pointsLink = node("a", "btn-block but mb10 padding-lg c-green mini-radius");
pointsLink.href = "javascript:;";
pointsLink.setAttribute("data-aksrt-vip-points-open", "");
pointsLink.innerHTML = '<svg class="icon" aria-hidden="true"><use xlink:href="#icon-points-color"></use></svg>'
+ "积分兑换会员"
+ '<i class="fa fas fa-angle-right em12 ml10" aria-hidden="true"></i>';
append(products, pointsLink);
}
if (catalog.more) append(products, node("div", "muted-2-color mb10 em09 muted-box padding-10 mini-radius", catalog.more));
renderMethods(products, methods, cardPass, wallet);
var actionName = actionKey(action);
var actionAllowed = allowed[actionName] === true;
var noMethod = methods.length === 0;
var reason = actionReason(reasons[actionName]);
var canPurchase = actionAllowed && !noMethod;
// 按钮字样对齐参考主题:会员弹窗即 zibpay_get_initiate_pay_input(4)
// 默认文案「立即支付」(开通/续费场景一致);无可用支付方式时渲染
// Zibll 同款 badg 提示,而非禁用按钮。
if (noMethod) {
append(products, node("span", "badg px12 c-yellow-2", "暂时无法支付,请与客服联系"));
} else {
var submit = node("button", "mt6 but jb-red initiate-pay btn-block radius", canPurchase ? "立即支付" : (reason || "暂不可购买"));
submit.type = "button";
submit.disabled = !canPurchase;
submit.setAttribute("data-vip-submit", "");
append(products, submit);
}
append(row, products);
append(form, row);
append(pane, form);
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));
// 卡密渠道初始显隐(选中 card_pass 时隐藏套餐、显示卡密输入盒)。
modal.querySelectorAll(".aksrt-vip-pane").forEach(function (pane) {
applyVipMethodVisibility(pane);
});
append(body, modal);
}
function selectedMethod(root) {
var hidden = root.querySelector('input[name="payment_method"]');
if (hidden && hidden.value) return hidden.value;
var first = root.querySelector('[data-for="payment_method"]');
return first ? first.getAttribute("data-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 = "";
},
});
}
// 卡密兑换会员(type=vip):直兑,不创建支付订单。
function redeemVipCardPass(pane, button) {
var pwEl = pane.querySelector('input[name="card_pass[password]"]');
var cardEl = pane.querySelector('input[name="card_pass[card]"]');
var password = pwEl ? String(pwEl.value || "").trim() : "";
if (!password) {
notyf("请输入卡密", "danger");
if (pwEl) pwEl.focus();
return;
}
if (!button || button.disabled) return;
var request = window.AksrtRequest;
if (!request || typeof request.request !== "function") {
notyf("会员兑换暂不可用", "danger");
return;
}
button.disabled = true;
button.classList.add("is-loading");
notyf("正在兑换会员…", "load", "", "aksrt-vip-purchase-toast");
var key = typeof request.createIdempotencyKey === "function" ? request.createIdempotencyKey() : "";
request.request("/pay/card-pass/redeem", {
method: "POST",
headers: key ? { "Idempotency-Key": key } : {},
body: JSON.stringify({
type: "vip",
card: cardEl ? String(cardEl.value || "").trim() : "",
password: password,
}),
}).then(function (response) {
button.disabled = false;
button.classList.remove("is-loading");
if (!response.ok || (response.json && response.json.code && !response.json.success)) {
notyf((response.json && response.json.message) || "兑换失败", "danger", 5, "aksrt-vip-purchase-toast");
return;
}
var data = response.data || (response.json && response.json.data) || {};
if (window.AksrtModalRuntime && typeof window.AksrtModalRuntime.invalidate === "function") {
window.AksrtModalRuntime.invalidate(["vip", "orders"]);
}
notyf(data.message || "兑换成功,正在刷新…", "success", 5, "aksrt-vip-purchase-toast");
window.setTimeout(function () { window.location.reload(); }, 800);
}).catch(function () {
button.disabled = false;
button.classList.remove("is-loading");
notyf("网络错误,请重试", "danger", 5, "aksrt-vip-purchase-toast");
});
}
function bindModalUi(root) {
if (!root || root.dataset.aksrtVipBound === "1") return;
root.dataset.aksrtVipBound = "1";
root.addEventListener("click", function (event) {
var methodItem = event.target.closest ? event.target.closest('[data-for="payment_method"]') : null;
if (methodItem) {
var pane = methodItem.closest(".aksrt-vip-pane") || root;
pane.querySelectorAll('[data-for="payment_method"]').forEach(function (el) {
el.classList.toggle("active", el === methodItem);
});
var hidden = pane.querySelector('input[name="payment_method"]');
if (hidden) hidden.value = methodItem.getAttribute("data-value") || "";
applyVipMethodVisibility(pane);
return;
}
var tab = event.target.closest("[data-vip-tab]");
if (tab) {
event.preventDefault();
var id = tab.getAttribute("data-vip-tab");
root.querySelectorAll("[data-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.classList.toggle("active", active);
pane.classList.toggle("in", active);
});
return;
}
var submit = event.target.closest("[data-vip-submit]");
if (submit) {
event.preventDefault();
purchaseFromPane(submit.closest(".aksrt-vip-pane") || root, submit);
}
});
}
function vipPay(trigger) {
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",
skeleton: '<div class="aksrt-vip-modal-panel"><div class="aksrt-vip-modal-content" data-aksrt-vip-modal-body><div class="text-center padding-20 muted-2-color">加载中…</div></div></div>',
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 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 = "";
}
});
// 积分兑换入口(data-aksrt-vip-points-open)文档级委托:一处绑定同时覆盖
// /user/vip/ 页面静态输出的入口横条与会员购买弹窗内运行时注入的同款按钮
// (事件冒泡至 document,弹窗内不再单独挂监听)。
function bindPointsEntry() {
document.addEventListener("click", function (event) {
var pointsLink = event.target.closest ? event.target.closest("[data-aksrt-vip-points-open]") : null;
if (!pointsLink) return;
event.preventDefault();
openPointsModal(pointsLink);
});
}
function bindOnce() {
if (window.__aksrtPayVipBound) return;
window.__aksrtPayVipBound = true;
document.addEventListener("click", onDocClick, true);
bindPointsModal();
bindPointsEntry();
}
jQuery(document).off("auto_fun.aksrt-pay-vip").on("auto_fun.aksrt-pay-vip", bindOnce);
})();