349 lines
11 KiB
JavaScript
349 lines
11 KiB
JavaScript
/**
|
|
* AKSRT VIP pay modal — zibll .pay-vip parity.
|
|
* Loads modal HTML from GET /aksrt/v1/pay/vip/modal then purchases via POST /pay/vip.
|
|
*/
|
|
(function () {
|
|
"use strict";
|
|
|
|
function restBase() {
|
|
var aksrt = window.aksrtData || window.aksrt || {};
|
|
var siteUrl = aksrt.siteUrl || aksrt.homeUrl || (window.location.origin + "/");
|
|
if (window.location.protocol === "https:" && siteUrl.substring(0, 5) === "http:") {
|
|
siteUrl = "https:" + siteUrl.substring(5);
|
|
}
|
|
var raw = aksrt.apiUrl || aksrt.restUrl || aksrt.rest_url || "";
|
|
if (raw) {
|
|
return String(raw).replace(/\/$/, "");
|
|
}
|
|
return siteUrl.replace(/\/$/, "") + "/index.php?rest_route=/aksrt/v1";
|
|
}
|
|
|
|
/**
|
|
* Join REST root + path [+ query].
|
|
* Pretty: /wp-json/aksrt/v1/pay/vip/modal?vip_level=1
|
|
* Plain: index.php?rest_route=/aksrt/v1/pay/vip/modal&vip_level=1
|
|
* Never put a second "?" after rest_route= (that 404s).
|
|
*/
|
|
function restUrl(path, query) {
|
|
path = String(path || "");
|
|
if (path.charAt(0) !== "/") path = "/" + path;
|
|
var base = restBase();
|
|
var qstr = "";
|
|
if (query && typeof query === "object") {
|
|
var parts = [];
|
|
Object.keys(query).forEach(function (k) {
|
|
if (query[k] === undefined || query[k] === null) return;
|
|
parts.push(encodeURIComponent(k) + "=" + encodeURIComponent(String(query[k])));
|
|
});
|
|
qstr = parts.join("&");
|
|
} else if (typeof query === "string" && query) {
|
|
qstr = query.replace(/^\?/, "");
|
|
}
|
|
|
|
if (base.indexOf("rest_route=") !== -1) {
|
|
var url = base + path;
|
|
if (qstr) url += "&" + qstr;
|
|
return url;
|
|
}
|
|
|
|
var pretty = base + path;
|
|
if (qstr) pretty += (pretty.indexOf("?") >= 0 ? "&" : "?") + qstr;
|
|
return pretty;
|
|
}
|
|
|
|
function nonceHeaders() {
|
|
var aksrt = window.aksrtData || window.aksrt || {};
|
|
var n = aksrt.restNonce || aksrt.nonce || "";
|
|
return n ? { "X-WP-Nonce": n } : {};
|
|
}
|
|
|
|
function api(path, options) {
|
|
var opts = Object.assign({}, options || {});
|
|
var query = opts.query;
|
|
if (Object.prototype.hasOwnProperty.call(opts, "query")) {
|
|
delete opts.query;
|
|
}
|
|
var onlyPath = String(path || "");
|
|
var inlineQ = null;
|
|
var qi = onlyPath.indexOf("?");
|
|
if (qi >= 0) {
|
|
inlineQ = onlyPath.slice(qi + 1);
|
|
onlyPath = onlyPath.slice(0, qi);
|
|
}
|
|
var url = restUrl(onlyPath, query || inlineQ);
|
|
|
|
var merged = Object.assign(
|
|
{
|
|
credentials: "same-origin",
|
|
headers: Object.assign({ "Content-Type": "application/json" }, nonceHeaders()),
|
|
},
|
|
opts
|
|
);
|
|
return fetch(url, merged).then(function (r) {
|
|
return r
|
|
.json()
|
|
.catch(function () {
|
|
return { message: "Invalid JSON (HTTP " + r.status + ")" };
|
|
})
|
|
.then(function (j) {
|
|
return { ok: r.ok, status: r.status, json: j };
|
|
});
|
|
});
|
|
}
|
|
function ensureModalShell() {
|
|
var el = document.getElementById("modal_pay_uservip");
|
|
if (el) return el;
|
|
el = document.createElement("div");
|
|
el.id = "modal_pay_uservip";
|
|
el.className = "aksrt-vip-modal-overlay";
|
|
el.setAttribute("aria-hidden", "true");
|
|
el.innerHTML =
|
|
'<div class="aksrt-vip-modal-dialog" role="dialog" aria-modal="true">' +
|
|
'<div class="aksrt-vip-modal-content" data-aksrt-vip-modal-body></div></div>';
|
|
document.body.appendChild(el);
|
|
el.addEventListener("click", function (e) {
|
|
if (e.target === el || e.target.closest(".aksrt-vip-modal-close")) {
|
|
closeModal();
|
|
}
|
|
});
|
|
document.addEventListener("keydown", function (e) {
|
|
if (e.key === "Escape") closeModal();
|
|
});
|
|
return el;
|
|
}
|
|
|
|
function openModal() {
|
|
var el = ensureModalShell();
|
|
el.classList.add("is-open");
|
|
el.setAttribute("aria-hidden", "false");
|
|
document.body.classList.add("aksrt-vip-modal-open");
|
|
}
|
|
|
|
function closeModal() {
|
|
var el = document.getElementById("modal_pay_uservip");
|
|
if (!el) return;
|
|
el.classList.remove("is-open");
|
|
el.setAttribute("aria-hidden", "true");
|
|
document.body.classList.remove("aksrt-vip-modal-open");
|
|
}
|
|
|
|
function bindModalUi(root) {
|
|
if (!root || root.dataset.aksrtVipBound === "1") return;
|
|
root.dataset.aksrtVipBound = "1";
|
|
|
|
root.addEventListener("click", function (e) {
|
|
var tab = e.target.closest("[data-vip-tab]");
|
|
if (tab) {
|
|
e.preventDefault();
|
|
var id = tab.getAttribute("data-vip-tab");
|
|
root.querySelectorAll(".aksrt-vip-tab").forEach(function (a) {
|
|
a.parentElement && a.parentElement.classList.remove("active");
|
|
});
|
|
tab.parentElement && tab.parentElement.classList.add("active");
|
|
root.querySelectorAll(".aksrt-vip-pane").forEach(function (p) {
|
|
var on = p.id === id;
|
|
p.hidden = !on;
|
|
p.classList.toggle("active", on);
|
|
});
|
|
return;
|
|
}
|
|
|
|
var submit = e.target.closest("[data-vip-submit]");
|
|
if (submit) {
|
|
e.preventDefault();
|
|
purchaseFromPane(submit.closest(".aksrt-vip-pane") || root, submit);
|
|
}
|
|
});
|
|
}
|
|
|
|
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('input[name="vip_product_id"]:checked');
|
|
return checked ? parseInt(checked.value, 10) : 0;
|
|
}
|
|
|
|
function setTip(root, text, isError) {
|
|
var tip = root.querySelector("[data-aksrt-vip-tip]");
|
|
if (!tip) return;
|
|
tip.textContent = text || "";
|
|
tip.classList.toggle("is-error", !!isError);
|
|
}
|
|
|
|
function pollOrder(orderNumber) {
|
|
if (!orderNumber) return;
|
|
var tries = 0;
|
|
var timer = setInterval(function () {
|
|
tries += 1;
|
|
api("/pay/content/order/" + encodeURIComponent(orderNumber))
|
|
.then(function (r) {
|
|
if (r.ok && r.json && r.json.paid) {
|
|
clearInterval(timer);
|
|
window.location.reload();
|
|
}
|
|
})
|
|
.catch(function () {});
|
|
if (tries >= 40) clearInterval(timer);
|
|
}, 3000);
|
|
}
|
|
|
|
function purchaseFromPane(pane, btn) {
|
|
var packageId = selectedPackage(pane);
|
|
if (!packageId) {
|
|
setTip(pane, "请选择套餐", true);
|
|
return;
|
|
}
|
|
if (btn.disabled) return;
|
|
btn.disabled = true;
|
|
btn.classList.add("is-loading");
|
|
setTip(pane, "正在创建订单…");
|
|
|
|
api("/pay/vip", {
|
|
method: "POST",
|
|
body: JSON.stringify({
|
|
package_id: packageId,
|
|
payment_method: selectedMethod(pane),
|
|
}),
|
|
})
|
|
.then(function (r) {
|
|
btn.disabled = false;
|
|
btn.classList.remove("is-loading");
|
|
if (!r.ok || (r.json && r.json.code && !r.json.success && r.json.success !== true)) {
|
|
var msg = (r.json && r.json.message) || "开通失败";
|
|
if (r.status === 401) {
|
|
msg = "请先登录";
|
|
document.querySelector(".signin-loader") && document.querySelector(".signin-loader").click();
|
|
}
|
|
setTip(pane, msg, true);
|
|
return;
|
|
}
|
|
var data = r.json && r.json.data ? r.json.data : r.json;
|
|
if (data && data.redirect && (data.payment_url || data.qr_content)) {
|
|
if (data.payment_url) window.open(data.payment_url, "_blank");
|
|
setTip(pane, data.message || "请完成支付,支付成功后将自动刷新。");
|
|
if (data.order_number) pollOrder(data.order_number);
|
|
return;
|
|
}
|
|
setTip(pane, (data && data.message) || "开通成功,正在刷新…");
|
|
setTimeout(function () {
|
|
window.location.reload();
|
|
}, 400);
|
|
})
|
|
.catch(function () {
|
|
btn.disabled = false;
|
|
btn.classList.remove("is-loading");
|
|
setTip(pane, "网络错误,请重试", true);
|
|
});
|
|
}
|
|
|
|
var loading = false;
|
|
|
|
function vipPay(trigger) {
|
|
var level = 1;
|
|
if (trigger) {
|
|
level = parseInt(trigger.getAttribute("vip-level") || trigger.getAttribute("data-vip-level") || "1", 10) || 1;
|
|
}
|
|
if (loading) return;
|
|
loading = true;
|
|
var telemetry = window.AksrtModalTelemetry;
|
|
var telemetryId = telemetry && typeof telemetry.start === "function" ? telemetry.start("vip") : "";
|
|
function telemetryMark(event) {
|
|
if (telemetry && telemetryId && typeof telemetry.mark === "function") telemetry.mark(telemetryId, event);
|
|
}
|
|
var shell = ensureModalShell();
|
|
var body = shell.querySelector("[data-aksrt-vip-modal-body]");
|
|
if (body) body.innerHTML = '<div class="text-center padding-20 muted-2-color">加载中…</div>';
|
|
openModal();
|
|
telemetryMark("shell");
|
|
|
|
api("/pay/vip/modal", { query: { vip_level: level } })
|
|
.then(function (r) {
|
|
loading = false;
|
|
if (r.status === 401) {
|
|
telemetryMark("unauthorized");
|
|
closeModal();
|
|
var login = document.querySelector(".signin-loader");
|
|
if (login) login.click();
|
|
return;
|
|
}
|
|
if (!r.ok || !r.json || !r.json.html) {
|
|
telemetryMark("error");
|
|
if (body) {
|
|
body.innerHTML =
|
|
'<div class="text-center padding-20 c-red">' +
|
|
((r.json && r.json.message) || "加载失败") +
|
|
"</div>";
|
|
}
|
|
return;
|
|
}
|
|
if (body) {
|
|
body.innerHTML = r.json.html;
|
|
telemetryMark("first-content");
|
|
bindModalUi(body);
|
|
telemetryMark("ready");
|
|
}
|
|
})
|
|
.catch(function () {
|
|
loading = false;
|
|
telemetryMark("error");
|
|
if (body) {
|
|
body.innerHTML = '<div class="text-center padding-20 c-red">网络错误</div>';
|
|
}
|
|
});
|
|
}
|
|
|
|
function onDocClick(e) {
|
|
// Header CTA, float rail, UC buttons, VIP hover cards.
|
|
var t = e.target.closest(".pay-vip, a.pay-vip, .float-btn.pay-vip, [data-aksrt-pay-vip]");
|
|
if (!t) return;
|
|
// Real external href without modal intent: leave alone.
|
|
var href = (t.getAttribute("href") || "").trim();
|
|
if (href && href !== "#" && href.indexOf("javascript:") !== 0 && !t.classList.contains("pay-vip") && !t.hasAttribute("data-aksrt-pay-vip") && !t.hasAttribute("vip-level") && !t.hasAttribute("data-vip-level")) {
|
|
return;
|
|
}
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
vipPay(t);
|
|
}
|
|
|
|
function init() {
|
|
// Capture phase so float/header anchors with href never navigate first.
|
|
document.addEventListener("click", onDocClick, true);
|
|
}
|
|
|
|
// Prefer module registry, but always bind once as hard fallback
|
|
// (float rail must work even if AksrtFrontend.rehydrate is a no-op).
|
|
if (window.__aksrtPayVipBound) {
|
|
// already initialized
|
|
} else if (window.AksrtFrontend && typeof window.AksrtFrontend.register === "function") {
|
|
window.AksrtFrontend.register("aksrt-pay-vip", function () {
|
|
if (window.__aksrtPayVipBound) return;
|
|
window.__aksrtPayVipBound = true;
|
|
init();
|
|
});
|
|
// Also bind immediately — float button is in footer, available now.
|
|
if (!window.__aksrtPayVipBound) {
|
|
window.__aksrtPayVipBound = true;
|
|
init();
|
|
}
|
|
} else if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
if (window.__aksrtPayVipBound) return;
|
|
window.__aksrtPayVipBound = true;
|
|
init();
|
|
});
|
|
} else {
|
|
window.__aksrtPayVipBound = true;
|
|
init();
|
|
}
|
|
|
|
window.aksrtVipPay = vipPay;
|
|
})();
|