Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
482 lines
16 KiB
JavaScript
482 lines
16 KiB
JavaScript
/**
|
|
* 充值页:套餐 / 支付方式互斥选择,按套餐 payMethods 刷新;提交走 VIP/金币下单。
|
|
* VIP: POST /api/vip/pay 金币: POST /api/coin/buy
|
|
* 支付方式严格使用接口返回的 payMethods,不造假默认渠道。
|
|
*/
|
|
(function () {
|
|
var submitting = false;
|
|
var confirmMask = document.querySelector("[data-deposit-confirm]");
|
|
var confirmMethods = document.querySelector("[data-deposit-confirm-methods]");
|
|
var confirmSubmit = document.querySelector("[data-deposit-confirm-submit]");
|
|
var bodyOverflowBeforeConfirm = "";
|
|
|
|
function toast(message, type) {
|
|
if (window.AuthAPI && typeof AuthAPI.toast === "function") {
|
|
AuthAPI.toast(message, type || "info");
|
|
return;
|
|
}
|
|
if (typeof showToast === "function") showToast(message, type || "info");
|
|
}
|
|
|
|
function bindExclusive(root, selector, activeClass) {
|
|
var scope = root || document;
|
|
var nodes = scope.querySelectorAll(selector);
|
|
if (!nodes.length) return;
|
|
nodes.forEach(function (node) {
|
|
node.addEventListener("click", function () {
|
|
nodes.forEach(function (item) {
|
|
item.classList.remove(activeClass);
|
|
});
|
|
node.classList.add(activeClass);
|
|
syncPayTypeAcross(node.getAttribute("data-pay-type") || "");
|
|
});
|
|
});
|
|
}
|
|
|
|
function syncPayTypeAcross(payType) {
|
|
if (!payType) return;
|
|
document.querySelectorAll("[data-pay-type]").forEach(function (el) {
|
|
var match = String(el.getAttribute("data-pay-type") || "") === String(payType);
|
|
if (el.classList.contains("deposit-pay-btn")) {
|
|
el.classList.toggle("deposit-pay-btn--active", match);
|
|
}
|
|
if (el.classList.contains("deposit-mobile-pay")) {
|
|
el.classList.toggle("deposit-mobile-pay--active", match);
|
|
}
|
|
if (el.classList.contains("deposit-confirm-method")) {
|
|
el.classList.toggle("is-active", match);
|
|
}
|
|
});
|
|
}
|
|
|
|
function parsePayMethods(raw) {
|
|
if (!raw) return [];
|
|
try {
|
|
var list = JSON.parse(raw);
|
|
if (!Array.isArray(list)) return [];
|
|
return list
|
|
.map(function (item) {
|
|
return {
|
|
type: String((item && item.type) || "").trim(),
|
|
typeName: String((item && (item.typeName || item.type)) || "").trim(),
|
|
};
|
|
})
|
|
.filter(function (item) {
|
|
return item.type || item.typeName;
|
|
});
|
|
} catch (e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function currentPayType() {
|
|
var active =
|
|
document.querySelector(".deposit-confirm-method.is-active[data-pay-type]") ||
|
|
document.querySelector(".deposit-pay-btn--active[data-pay-type]") ||
|
|
document.querySelector(".deposit-mobile-pay--active[data-pay-type]");
|
|
return active ? String(active.getAttribute("data-pay-type") || "") : "";
|
|
}
|
|
|
|
function currentPackage() {
|
|
return (
|
|
document.querySelector(".deposit-mobile-pkg--active[data-package-id]") ||
|
|
document.querySelector(".deposit-package--active[data-package-id]")
|
|
);
|
|
}
|
|
|
|
function renderPayButtons(container, className, activeClass, methods, preferType) {
|
|
if (!container) return;
|
|
var list = Array.isArray(methods) ? methods : [];
|
|
if (!list.length) {
|
|
container.innerHTML = '<div class="deposit-package-empty">暂无可用支付方式</div>';
|
|
return;
|
|
}
|
|
var prefer = preferType || "";
|
|
var activeType = prefer;
|
|
if (!list.some(function (item) { return item.type === prefer; })) {
|
|
activeType = list[0] ? list[0].type : "";
|
|
}
|
|
container.innerHTML = list
|
|
.map(function (item) {
|
|
var isActive = item.type === activeType;
|
|
return (
|
|
'<button class="' +
|
|
className +
|
|
(isActive ? " " + activeClass : "") +
|
|
'" type="button" data-pay-type="' +
|
|
escapeAttr(item.type) +
|
|
'">' +
|
|
escapeHtml(item.typeName || item.type) +
|
|
"</button>"
|
|
);
|
|
})
|
|
.join("");
|
|
bindExclusive(container, "." + className.split(" ").join("."), activeClass);
|
|
}
|
|
|
|
function escapeHtml(text) {
|
|
return String(text || "")
|
|
.replace(/&/g, "&")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, """);
|
|
}
|
|
|
|
function escapeAttr(text) {
|
|
return escapeHtml(text).replace(/'/g, "'");
|
|
}
|
|
|
|
function payMethodVisual(item) {
|
|
var value = String((item && (item.typeName || item.type)) || "").toLowerCase();
|
|
if (/支付宝|alipay/.test(value)) {
|
|
return { kind: "alipay", icon: "/static/images/figma-dialogs/alipay.svg" };
|
|
}
|
|
if (/微信|wechat|weixin|wxpay/.test(value)) {
|
|
return { kind: "wechat", icon: "/static/images/figma-dialogs/wechat.svg" };
|
|
}
|
|
if (/usdt|tether/.test(value)) {
|
|
return { kind: "usdt", icon: "/static/images/figma-dialogs/usdt.svg" };
|
|
}
|
|
return { kind: "other", icon: "" };
|
|
}
|
|
|
|
function renderConfirmMethods(methods, preferType) {
|
|
if (!confirmMethods) return "";
|
|
var list = Array.isArray(methods) ? methods : [];
|
|
var activeType = preferType || (list[0] && list[0].type) || "";
|
|
if (!list.some(function (item) { return item.type === activeType; })) {
|
|
activeType = (list[0] && list[0].type) || "";
|
|
}
|
|
confirmMethods.innerHTML = list.map(function (item) {
|
|
var visual = payMethodVisual(item);
|
|
var icon = visual.icon
|
|
? '<img src="' + visual.icon + '" alt="" />'
|
|
: '<span aria-hidden="true">¥</span>';
|
|
return '<button class="deposit-confirm-method' +
|
|
(item.type === activeType ? ' is-active' : '') +
|
|
'" type="button" data-pay-type="' + escapeAttr(item.type) + '">' +
|
|
'<span class="deposit-confirm-method-copy"><span class="deposit-confirm-method-icon deposit-confirm-method-icon--' + visual.kind + '">' + icon + '</span>' +
|
|
'<span class="deposit-confirm-method-name">' + escapeHtml(item.typeName || item.type) + '</span></span>' +
|
|
'<span class="deposit-confirm-method-check" aria-hidden="true">✓</span></button>';
|
|
}).join("");
|
|
confirmMethods.querySelectorAll(".deposit-confirm-method").forEach(function (button) {
|
|
button.addEventListener("click", function () {
|
|
syncPayTypeAcross(button.getAttribute("data-pay-type") || "");
|
|
});
|
|
});
|
|
syncPayTypeAcross(activeType);
|
|
return activeType;
|
|
}
|
|
|
|
function hasPayMethods(el) {
|
|
var target = el || currentPackage();
|
|
if (!target) return false;
|
|
return parsePayMethods(target.getAttribute("data-pay-methods")).length > 0;
|
|
}
|
|
|
|
function syncPayMethods(el) {
|
|
var methods = parsePayMethods(el.getAttribute("data-pay-methods"));
|
|
var prefer = currentPayType();
|
|
renderPayButtons(
|
|
document.querySelector("[data-deposit-pay-pc]"),
|
|
"deposit-pay-btn",
|
|
"deposit-pay-btn--active",
|
|
methods,
|
|
prefer
|
|
);
|
|
renderPayButtons(
|
|
document.querySelector("[data-deposit-pay-mobile]"),
|
|
"deposit-mobile-pay",
|
|
"deposit-mobile-pay--active",
|
|
methods,
|
|
prefer
|
|
);
|
|
}
|
|
|
|
function syncPackageActive(kind, packageId) {
|
|
document.querySelectorAll(".deposit-package, .deposit-mobile-pkg").forEach(function (el) {
|
|
var match =
|
|
String(el.getAttribute("data-package-id") || "") === String(packageId) &&
|
|
String(el.getAttribute("data-package-kind") || "") === String(kind);
|
|
el.classList.toggle("deposit-package--active", match && el.classList.contains("deposit-package"));
|
|
el.classList.toggle(
|
|
"deposit-mobile-pkg--active",
|
|
match && el.classList.contains("deposit-mobile-pkg")
|
|
);
|
|
});
|
|
}
|
|
|
|
function updateSubmitButtons(price, loading) {
|
|
var canPay = hasPayMethods(currentPackage());
|
|
var disabled = !!loading || !canPay;
|
|
var pkg = currentPackage();
|
|
var packageName = pkg ? String(pkg.getAttribute("data-package-name") || "").trim() : "";
|
|
var text;
|
|
if (loading) {
|
|
text = "支付中...";
|
|
} else if (!canPay) {
|
|
text = "暂无支付方式";
|
|
} else {
|
|
text = "立即支付 ¥" + (price || "0");
|
|
}
|
|
document.querySelectorAll("[data-deposit-submit]").forEach(function (btn) {
|
|
btn.disabled = disabled;
|
|
btn.classList.toggle("is-loading", !!loading);
|
|
btn.classList.toggle("is-disabled", !canPay && !loading);
|
|
if (btn.classList.contains("deposit-pc-submit")) {
|
|
btn.textContent = loading ? "支付中..." : canPay ? "立即支付 ¥" + (price || "0") : "暂无支付方式";
|
|
} else {
|
|
btn.textContent = loading ? "支付中..." : canPay ? "支付 ¥" + (price || "0") + " 充值金币" : text;
|
|
}
|
|
});
|
|
document.querySelectorAll("[data-deposit-selected-note]").forEach(function (node) {
|
|
node.textContent = packageName
|
|
? "已选择 " + packageName + " · 点击后选择支付方式"
|
|
: "请选择金币套餐";
|
|
});
|
|
}
|
|
|
|
function onPackageClick(el) {
|
|
var kind = el.getAttribute("data-package-kind") || "";
|
|
var id = el.getAttribute("data-package-id") || "";
|
|
var price = el.getAttribute("data-price") || "0";
|
|
syncPackageActive(kind, id);
|
|
syncPayMethods(el);
|
|
updateSubmitButtons(price, false);
|
|
}
|
|
|
|
function openPayUrl(data) {
|
|
var url = data && data.payUrl ? String(data.payUrl).trim() : "";
|
|
if (!url) {
|
|
toast("未获取到支付链接", "error");
|
|
return;
|
|
}
|
|
var mode = String((data && data.payUrlMode) || "").toLowerCase();
|
|
var openNew =
|
|
!!(data && data.isOpenNewBrowser) ||
|
|
mode === "new" ||
|
|
mode === "blank" ||
|
|
mode === "external";
|
|
if (openNew) {
|
|
var win = window.open(url, "_blank");
|
|
if (!win) {
|
|
window.location.href = url;
|
|
}
|
|
return;
|
|
}
|
|
window.location.href = url;
|
|
}
|
|
|
|
function ensureLoggedIn() {
|
|
if (window.CookieUtil && CookieUtil.isLoggedIn()) return true;
|
|
toast("请先登录后再支付", "error");
|
|
var loginURL = "/login/return/deposit/";
|
|
if (typeof window.openLoginDialog === "function") {
|
|
var opened = window.openLoginDialog();
|
|
if (opened && typeof opened.then === "function") {
|
|
opened.catch(function () {
|
|
window.location.href = loginURL;
|
|
});
|
|
}
|
|
} else {
|
|
window.location.href = loginURL;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function closePayConfirm() {
|
|
if (!confirmMask) return;
|
|
confirmMask.classList.remove("is-open");
|
|
confirmMask.setAttribute("aria-hidden", "true");
|
|
document.body.style.overflow = bodyOverflowBeforeConfirm;
|
|
}
|
|
|
|
function openPayConfirm() {
|
|
if (submitting || !confirmMask) return;
|
|
if (!ensureLoggedIn()) return;
|
|
var pkg = currentPackage();
|
|
if (!pkg) {
|
|
toast("请选择套餐", "error");
|
|
return;
|
|
}
|
|
var methods = parsePayMethods(pkg.getAttribute("data-pay-methods"));
|
|
if (!methods.length) {
|
|
toast("当前套餐暂无可用支付方式", "error");
|
|
return;
|
|
}
|
|
var packageName = String(pkg.getAttribute("data-package-name") || "金币套餐").trim();
|
|
var price = String(pkg.getAttribute("data-price") || "0").trim();
|
|
var nameEl = confirmMask.querySelector("[data-deposit-confirm-name]");
|
|
var priceEl = confirmMask.querySelector("[data-deposit-confirm-price]");
|
|
if (nameEl) nameEl.textContent = packageName;
|
|
if (priceEl) priceEl.textContent = price;
|
|
renderConfirmMethods(methods, currentPayType());
|
|
bodyOverflowBeforeConfirm = document.body.style.overflow;
|
|
document.body.style.overflow = "hidden";
|
|
confirmMask.classList.add("is-open");
|
|
confirmMask.setAttribute("aria-hidden", "false");
|
|
}
|
|
|
|
function submitPay() {
|
|
if (submitting) return;
|
|
if (!window.HttpRequest) {
|
|
toast("请求组件未加载", "error");
|
|
return;
|
|
}
|
|
if (!ensureLoggedIn()) return;
|
|
|
|
var pkg = currentPackage();
|
|
if (!pkg) {
|
|
toast("请选择套餐", "error");
|
|
return;
|
|
}
|
|
var packageId = Number(pkg.getAttribute("data-package-id") || 0);
|
|
var kind = String(pkg.getAttribute("data-package-kind") || "");
|
|
var price = pkg.getAttribute("data-price") || "0";
|
|
var payMode = currentPayType();
|
|
if (!packageId || (kind !== "vip" && kind !== "coin")) {
|
|
toast("套餐无效,请重新选择", "error");
|
|
return;
|
|
}
|
|
if (!payMode) {
|
|
toast("请选择支付方式", "error");
|
|
return;
|
|
}
|
|
if (!hasPayMethods(pkg)) {
|
|
toast("当前套餐暂无可用支付方式", "error");
|
|
return;
|
|
}
|
|
|
|
var api = kind === "coin" ? "/api/coin/buy" : "/api/vip/pay";
|
|
submitting = true;
|
|
updateSubmitButtons(price, true);
|
|
if (confirmSubmit) {
|
|
confirmSubmit.disabled = true;
|
|
confirmSubmit.textContent = "支付中...";
|
|
}
|
|
|
|
HttpRequest.post(api, { packageId: packageId, payMode: payMode })
|
|
.then(function (res) {
|
|
if (!res || res.code !== 200 || !res.data) {
|
|
var err = new Error((res && (res.tip || res.msg)) || "发起支付失败");
|
|
err.code = res && res.code;
|
|
throw err;
|
|
}
|
|
openPayUrl(res.data);
|
|
})
|
|
.catch(function (err) {
|
|
if (err && err.code === 401) {
|
|
toast("请先登录后再支付", "error");
|
|
if (typeof window.openLoginDialog === "function") {
|
|
var opened = window.openLoginDialog();
|
|
if (opened && typeof opened.then === "function") {
|
|
opened.catch(function () {
|
|
window.location.href = "/login/return/deposit/";
|
|
});
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
toast((err && err.message) || "发起支付失败", "error");
|
|
})
|
|
.finally(function () {
|
|
submitting = false;
|
|
updateSubmitButtons(price, false);
|
|
if (confirmSubmit) {
|
|
confirmSubmit.disabled = false;
|
|
confirmSubmit.textContent = "去支付";
|
|
}
|
|
});
|
|
}
|
|
|
|
bindExclusive(document, ".deposit-pay-btn", "deposit-pay-btn--active");
|
|
bindExclusive(document, ".deposit-mobile-pay", "deposit-mobile-pay--active");
|
|
|
|
document.querySelectorAll(".deposit-package[data-package-id]").forEach(function (node) {
|
|
node.addEventListener("click", function () {
|
|
onPackageClick(node);
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll(".deposit-mobile-pkg[data-package-id]").forEach(function (node) {
|
|
node.addEventListener("click", function () {
|
|
onPackageClick(node);
|
|
});
|
|
});
|
|
|
|
document.querySelectorAll("[data-deposit-submit]").forEach(function (btn) {
|
|
btn.addEventListener("click", openPayConfirm);
|
|
});
|
|
|
|
if (confirmSubmit) confirmSubmit.addEventListener("click", submitPay);
|
|
document.querySelectorAll("[data-deposit-confirm-close]").forEach(function (button) {
|
|
button.addEventListener("click", closePayConfirm);
|
|
});
|
|
if (confirmMask) {
|
|
confirmMask.addEventListener("click", function (event) {
|
|
if (event.target === confirmMask) closePayConfirm();
|
|
});
|
|
}
|
|
document.addEventListener("keydown", function (event) {
|
|
if (event.key === "Escape" && confirmMask && confirmMask.classList.contains("is-open")) {
|
|
closePayConfirm();
|
|
}
|
|
});
|
|
|
|
|
|
var active =
|
|
document.querySelector(".deposit-mobile-pkg--active[data-price]") ||
|
|
document.querySelector(".deposit-package--active[data-price]");
|
|
if (active) {
|
|
syncPayMethods(active);
|
|
updateSubmitButtons(active.getAttribute("data-price") || "0", false);
|
|
} else {
|
|
updateSubmitButtons("0", false);
|
|
}
|
|
|
|
// /deposit/coin 时滚动到金币充值区域(套餐默认选中由 SSR 完成)
|
|
(function scrollPreferCoin() {
|
|
if (!/\/deposit\/coin\/?$/i.test(window.location.pathname || "")) return;
|
|
var isMobile = window.matchMedia("(max-width: 767px)").matches;
|
|
var target = isMobile
|
|
? document.getElementById("depositCoinMobileTitle")
|
|
: document.getElementById("depositCoinGroup");
|
|
if (!target) return;
|
|
setTimeout(function () {
|
|
target.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
}, 80);
|
|
})();
|
|
|
|
function formatRecharge(value) {
|
|
var n = Number(value);
|
|
if (!isFinite(n) || n < 0) n = 0;
|
|
return String(Math.floor(n)).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
}
|
|
|
|
function syncDepositRecharge(value) {
|
|
var text = formatRecharge(value);
|
|
document.querySelectorAll("[data-deposit-recharge]").forEach(function (el) {
|
|
el.textContent = text;
|
|
});
|
|
}
|
|
|
|
function refreshDepositWallet() {
|
|
if (!window.CookieUtil || !CookieUtil.isLoggedIn() || !window.HttpRequest) {
|
|
return;
|
|
}
|
|
var session = CookieUtil.getAuthSession();
|
|
if (session && session.recharge != null && session.recharge !== "") {
|
|
syncDepositRecharge(session.recharge);
|
|
}
|
|
HttpRequest.get("/api/auth/getInfo")
|
|
.then(function (res) {
|
|
if (!res || res.code !== 200 || !res.data) return;
|
|
if (CookieUtil.setAuthSession) CookieUtil.setAuthSession(res.data);
|
|
syncDepositRecharge(res.data.recharge);
|
|
})
|
|
.catch(function () {});
|
|
}
|
|
|
|
refreshDepositWallet();
|
|
})();
|