const $ = (id) => document.getElementById(id); let pendingOrderNo = ""; let pendingPollTimer = null; let pendingPollCount = 0; function setStatus(msg, danger = false) { const el = $("status"); if (!el) return; el.style.color = danger ? "#b42318" : "#0f5f3d"; el.textContent = msg; } function setLoading(button, loading, idleText, loadingText) { if (!button) return; button.disabled = loading; button.textContent = loading ? loadingText : idleText; } function importantNotice(message, title = "提示") { if (typeof window.uiAlert === "function") { void window.uiAlert(message, title); return; } setStatus(message); } function openPayLink(url) { const payUrl = String(url || "").trim(); if (!payUrl) return false; const tab = window.open(payUrl, "_blank", "noopener"); if (tab) return true; window.location.href = payUrl; return true; } async function postJSON(url, body) { const res = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); const data = await res.json(); if (!res.ok) throw new Error(data.detail || "请求失败"); return data; } async function fetchMe() { const res = await fetch("/api/auth/me"); const data = await res.json(); if (!data.logged_in) { window.location.href = "/auth?next=/upgrade"; return null; } return data; } function formatDateTime(tsSec) { const n = Number(tsSec || 0); if (!n) return "-"; return new Date(n * 1000).toLocaleString(); } function formatDate(tsSec) { const n = Number(tsSec || 0); if (!n) return "-"; const d = new Date(n * 1000); const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); return `${y}-${m}-${day}`; } function refreshPurchaseCycleText() { const el = $("purchaseCycleText"); if (!el) return; const start = Math.floor(Date.now() / 1000); const end = start + 30 * 24 * 3600; el.textContent = `时长:1个月(到期:${formatDate(end)})`; } function renderVip(vip) { const shared = Number(vip.shared_credits || vip.token_balance || 0); const seatRemaining = Number(vip.seat_remaining_credits || 0); const totalAvailable = Number(vip.total_available_credits || seatRemaining + shared); const enabled = Boolean(vip.vip_enabled); const cycleStart = Number(vip.cycle_started_at || 0); const cycleEnd = Number(vip.cycle_expires_at || 0); const now = Math.floor(Date.now() / 1000); const hasActiveSubscription = cycleStart > 0 && cycleEnd > now; if ($("upgradeTokenBalance")) $("upgradeTokenBalance").textContent = String(totalAvailable); if ($("upgradeTokenBalanceHero")) $("upgradeTokenBalanceHero").textContent = String(totalAvailable); if ($("vipTokenBalance")) $("vipTokenBalance").textContent = String(shared); if ($("vipSeatRemaining")) $("vipSeatRemaining").textContent = String(seatRemaining); if ($("vipTotalConsumed")) $("vipTotalConsumed").textContent = String(Number(vip.total_consumed_tokens || 0)); if ($("vipEnabledSelect")) $("vipEnabledSelect").value = enabled ? "1" : "0"; if ($("vipStateText")) { $("vipStateText").textContent = totalAvailable <= 0 ? "额度已用完" : enabled ? "平台模型已开启" : "平台模型已关闭"; } if ($("vipCycleHint")) { if (cycleStart > 0 && cycleEnd > 0) { const startText = formatDateTime(cycleStart); const endText = formatDateTime(cycleEnd); $("vipCycleHint").textContent = `当前周期:${startText} - ${endText}(到期自动清零)`; } else { $("vipCycleHint").textContent = "当前未开始月周期,首次支付成功后开始计时。"; } } if ($("vipModeHint")) { if (enabled && hasActiveSubscription) { $("vipModeHint").textContent = "当前模型模式:平台模型(订阅有效)"; } else if (enabled && !hasActiveSubscription) { $("vipModeHint").textContent = "当前模型模式:自由模型(未开通订阅)"; } else { $("vipModeHint").textContent = "当前模型模式:自由模型"; } } if (totalAvailable <= 0) { setStatus("Credits 额度已用完,请充值共享加油包或等待下个计费周期。", true); } } async function refresh() { const me = await fetchMe(); if (me && me.vip) renderVip(me.vip); } async function fetchBillingOverview() { const res = await fetch("/api/billing/overview"); const data = await res.json(); if (!res.ok || !data.ok) throw new Error(data.detail || "账单读取失败"); return data; } function stopPendingPoll() { if (pendingPollTimer) { window.clearInterval(pendingPollTimer); pendingPollTimer = null; } pendingPollCount = 0; } function startPendingPoll(orderNo, showCreatedNotice = true) { if (!orderNo) return; pendingOrderNo = orderNo; stopPendingPoll(); const msg = `订单 ${orderNo} 已创建,请完成支付,系统将自动刷新余额。`; setStatus(msg); if (showCreatedNotice) { importantNotice(msg, "订单已创建"); } pendingPollTimer = window.setInterval(async () => { pendingPollCount += 1; try { const data = await fetchBillingOverview(); const records = Array.isArray(data.recharge_records) ? data.recharge_records : []; const current = records.find((r) => (r.order_no || "") === pendingOrderNo); if (current && (current.status || "") === "paid") { stopPendingPoll(); pendingOrderNo = ""; setStatus("支付成功,余额已自动刷新。"); await refresh(); return; } if (current && ["cancelled", "closed"].includes(String(current.status || "").toLowerCase())) { stopPendingPoll(); pendingOrderNo = ""; setStatus("订单超过15分钟未支付,已自动取消。", true); await refresh(); return; } if (pendingPollCount >= 120) { stopPendingPoll(); setStatus("订单仍未支付,可支付后点击刷新查看余额。", true); } } catch { if (pendingPollCount >= 120) { stopPendingPoll(); } } }, 3000); } const saveVipBtn = $("saveVipBtn"); const vipRechargeBtn = $("vipRechargeBtn"); const vipRechargeTokensInput = $("vipRechargeTokens"); const vipRechargeAmountInput = $("vipRechargeAmount"); const payChannelOptions = Array.from(document.querySelectorAll(".pay-channel-option")); let selectedPayChannel = "wechat"; function bindPayChannelOptions() { if (!payChannelOptions.length) return; payChannelOptions.forEach((btn) => { btn.addEventListener("click", () => { selectedPayChannel = (btn.dataset.channel || "wechat").trim() || "wechat"; payChannelOptions.forEach((item) => { const active = item === btn; item.classList.toggle("is-active", active); item.setAttribute("aria-pressed", active ? "true" : "false"); }); }); }); } function packageRate() { const credits = Number((vipRechargeTokensInput && vipRechargeTokensInput.dataset.packageCredits) || "1500"); const amount = Number((vipRechargeAmountInput && vipRechargeAmountInput.dataset.packageAmount) || "19.9"); return { packageCredits: Number.isFinite(credits) && credits > 0 ? credits : 1500, packageAmount: Number.isFinite(amount) && amount > 0 ? amount : 19.9, }; } function syncRechargeAmount() { const { packageCredits, packageAmount } = packageRate(); const credits = packageCredits; const amount = packageAmount; if (vipRechargeTokensInput) vipRechargeTokensInput.value = String(credits); vipRechargeAmountInput.value = amount.toFixed(2); if ($("purchaseCredits")) $("purchaseCredits").textContent = String(credits); if ($("purchaseAmount")) $("purchaseAmount").textContent = `¥${amount.toFixed(2)}`; } if (vipRechargeAmountInput) vipRechargeAmountInput.readOnly = true; syncRechargeAmount(); refreshPurchaseCycleText(); bindPayChannelOptions(); if (saveVipBtn) { saveVipBtn.addEventListener("click", async () => { setLoading(saveVipBtn, true, "保存升级设置", "保存中..."); try { const enabled = (($("vipEnabledSelect") && $("vipEnabledSelect").value) || "0") === "1"; const out = await postJSON("/api/auth/vip/toggle", { enabled }); if (!out.ok) { setStatus(out.detail || "VIP 设置保存失败", true); return; } setStatus("升级设置已保存。"); await refresh(); } catch (e) { setStatus(e.message || "VIP 设置保存失败", true); } finally { setLoading(saveVipBtn, false, "保存升级设置", "保存中..."); } }); } if (vipRechargeBtn) { vipRechargeBtn.addEventListener("click", async () => { setLoading(vipRechargeBtn, true, "订阅", "创建订单中..."); try { const tokens = Number((($("vipRechargeTokens") && $("vipRechargeTokens").value) || "0").trim()); const amount = Number((($("vipRechargeAmount") && $("vipRechargeAmount").value) || "0").trim()); if (!Number.isFinite(tokens) || tokens <= 0) { setStatus("请输入正确的 Credits 数量", true); return; } if (!Number.isFinite(amount) || amount <= 0) { setStatus("请输入正确的支付金额", true); return; } const out = await postJSON("/api/pay/wechat/", { tokens: Math.round(tokens), amount_cny: Number(amount.toFixed(2)), channel: selectedPayChannel || "wechat", subscriber_name: "", subscriber_phone: "", shipping_address: "", }); if (!out.ok) { setStatus(out.detail || "充值失败", true); return; } const orderNo = out.order && out.order.order_no ? out.order.order_no : ""; if (orderNo) startPendingPoll(orderNo, true); if (out.pay_url) { openPayLink(out.pay_url); return; } const tip = "订单已创建,但未获取到支付链接,请检查支付网关配置。"; setStatus(tip, true); importantNotice(tip, "支付链接缺失"); } catch (e) { setStatus(e.message || "充值失败", true); } finally { setLoading(vipRechargeBtn, false, "订阅", "创建订单中..."); } }); } refresh(); (async () => { await fetchMe(); try { const data = await fetchBillingOverview(); const records = Array.isArray(data.recharge_records) ? data.recharge_records : []; const pending = records.find((r) => (r.status || "") === "pending"); if (pending && pending.order_no) startPendingPoll(pending.order_no, false); } catch { // ignore } })(); window.setInterval(refreshPurchaseCycleText, 60000);