Files
bili-auto-comment/buy.js
T
jinghui 226e4fbbf9 v1.10.3: 修「买完仍显示试用倒计时」与「到期时间显示永久」
根因:CardAuth 自动发卡只写 ca_cards(status=used + bind_info.machine_id),
从不写 ca_authorizations,而插件只查 /public/authorizations/query → 已购用户恒为空。

- checkLicense 增加卡密直验兜底:用本地记住的卡密调 /public/verify,
  返回 valid/expire_time/is_permanent,直接作为授权状态与到期时间
- activateCard 成功即持久化 activeCardKey + activeCardInfo(换机/过期自动清除)
- buy.js 不再用「永久」兜底到期时间(订单接口不返回 expire_time),
  改为只在服务端明确给出时显示,否则显示套餐名
- 侧边栏已授权态显示套餐名 + 真实到期时间
2026-10-20 23:32:43 +08:00

200 lines
8.6 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 当前商品选择页:选套餐 → 选支付方式 → 下单 → 支付 → 自动激活本机 */
const $ = (id) => document.getElementById(id);
let packages = [];
let selected = null;
let orderNo = null;
let pollTimer = null;
function esc(s) {
return String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({
'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
}[c]));
}
function durationText(days) {
const n = Number(days);
if (!n) return '限时体验';
if (n >= 999999) return '永久有效';
return n + ' 天';
}
async function loadPackages() {
$('package-list').innerHTML = '<div class="loading">正在加载当前商品…</div>';
try {
const res = await chrome.runtime.sendMessage({ action: 'getCardTypes' });
if (!res || !res.ok) throw new Error((res && res.message) || '加载失败');
packages = (res.data || []).filter((p) => Number(p.price) > 0);
if (!packages.length) {
$('package-list').innerHTML = '<div class="empty">当前暂无在售商品,请稍后再试</div>';
return;
}
renderPackages();
} catch (e) {
$('package-list').innerHTML = '<div class="empty">商品加载失败:' + esc(e.message) + '</div>';
}
}
function renderPackages() {
$('package-list').innerHTML = packages.map((p) => `
<div class="package" data-id="${esc(p.id)}">
<div class="pkg-name">${esc(p.name)}</div>
<div class="pkg-price">¥${esc(p.price)}</div>
<div class="pkg-dur">${esc(durationText(p.duration_days))}</div>
<button class="pkg-btn" data-id="${esc(p.id)}">选择此商品</button>
</div>`).join('');
document.querySelectorAll('.pkg-btn').forEach((btn) => {
btn.addEventListener('click', () => selectPackage(btn.dataset.id));
});
}
function selectPackage(id) {
selected = packages.find((p) => String(p.id) === String(id));
if (!selected) return;
document.querySelectorAll('.package').forEach((el) => {
el.classList.toggle('active', el.dataset.id === String(id));
});
$('chosen-package').innerHTML = `已选商品:<b>${esc(selected.name)}</b> 价格 <b>¥${esc(selected.price)}</b> ${esc(durationText(selected.duration_days))}`;
$('step-pay').hidden = false;
$('step-pay').scrollIntoView({ behavior: 'smooth', block: 'center' });
}
async function createOrder(payType) {
if (!selected) return;
$('order-panel').hidden = false;
$('order-panel').scrollIntoView({ behavior: 'smooth', block: 'center' });
const status = $('order-status');
status.className = 'order-status pending';
status.textContent = '⏳ 正在创建订单…';
try {
const res = await chrome.runtime.sendMessage({
action: 'authCreateOrder',
payType,
cardTypeId: selected.id
});
if (!res || !res.ok) throw new Error((res && res.message) || '创建订单失败');
orderNo = res.order.orderNo;
status.textContent = `订单 ${orderNo}(¥${res.order.amount})已创建,正在打开支付页面…`;
if (res.payUrl) window.open(res.payUrl, '_blank');
startPolling();
} catch (e) {
status.className = 'order-status error';
status.textContent = '✗ ' + e.message;
}
}
function startPolling() {
if (pollTimer) clearInterval(pollTimer);
let attempts = 0;
pollTimer = setInterval(async () => {
attempts++;
if (attempts > 240) {
clearInterval(pollTimer);
pollTimer = null;
const status = $('order-status');
status.className = 'order-status error';
status.textContent = `✗ 等待支付超时。订单号 ${orderNo},如已付款请把订单号发给官网客服处理。`;
return;
}
try {
const res = await chrome.runtime.sendMessage({ action: 'authQueryOrder', orderNo });
if (!res || !res.ok) return;
// 只要拿到卡密就先展示出来,不等激活结果——自动激活失败时用户手里必须有码
if (res.cardKey) showCardBox(res.cardKey, !!res.activated);
if (res.activated) {
clearInterval(pollTimer);
pollTimer = null;
showActivated(res);
} else if (res.cardKey) {
clearInterval(pollTimer);
pollTimer = null;
const status = $('order-status');
status.className = 'order-status error';
status.innerHTML = '✗ <b>已支付并已发卡</b>,但自动激活未成功(' +
esc(res.message || '本机绑定可能被占用') + ')。请用上方卡密手动激活:回到插件侧边栏 → 卡密输入框粘贴 → 点「粘贴卡密并激活」。<br>' +
'订单号:<b>' + esc(orderNo || '') + '</b>';
} else if (res.paid) {
// 已付款但服务端还没把卡发出来(最常见原因是卡池库存不足)
const waited = attempts * 5;
const status = $('order-status');
status.className = 'order-status pending';
status.textContent = '⏳ 已支付,正在为你发卡…(订单 ' + orderNo + ',已等待 ' + waited + ' 秒)';
if (waited >= 90) {
status.className = 'order-status error';
status.innerHTML = '⏳ <b>已支付</b>,但发卡耗时较长(已 ' + waited + ' 秒)。订单 <b>' + esc(orderNo) +
'</b> 付款状态为已支付,<b>权益不会丢失</b>:继续等会自动发放,或凭此订单号联系客服补发。';
}
} else {
const status = $('order-status');
status.className = 'order-status pending';
status.textContent = '⏳ 等待支付中…(订单 ' + orderNo + ',已等待 ' + attempts * 5 + ' 秒)';
}
} catch (e) {
/* 单次查询失败不中断轮询 */
}
}, 5000);
}
/** 支付成功后立刻把卡密展示给用户(含自动激活失败的场景) */
function showCardBox(key, activated) {
const box = $('card-box');
if (!box || !key) return;
if (!box.hidden && $('cb-key-text').textContent === key) return;
$('cb-key-text').textContent = key;
box.hidden = false;
$('cb-copy-tip').textContent = activated ? '已自动激活本机,这是你的卡密,建议留存' : '';
box.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
function showActivated(res) {
const status = $('order-status');
const permanent = !!res.isPermanent;
const exp = String(res.expireTime || '').trim();
const plan = String(res.planName || '').trim();
// 绝不默认「永久」:到期时间只在服务端明确给出时才显示
let expireLine;
if (permanent) expireLine = '到期时间:<b>永久</b>';
else if (exp) expireLine = '到期时间:<b>' + esc(exp) + '</b>';
else if (plan) expireLine = '套餐:<b>' + esc(plan) + '</b>(实际到期时间以插件侧边栏「已授权 · 到期 …」为准)';
else expireLine = '到期时间:以插件侧边栏显示为准';
status.className = 'order-status success';
status.innerHTML = `
<div class="ok-title">✓ 支付成功,已自动激活</div>
<div class="ok-line">卡密:<b>${esc(res.cardKey || '')}</b></div>
<div class="ok-line">${expireLine}</div>
<div class="ok-line muted">回到插件侧边栏即可使用全部功能(本机已授权)。</div>`;
}
document.querySelectorAll('.pay-btn').forEach((btn) => {
btn.addEventListener('click', () => createOrder(btn.dataset.pay));
});
// 复制卡密:优先 clipboard API,无权限时降级 execCommand
$('cb-copy') && $('cb-copy').addEventListener('click', async () => {
const key = ($('cb-key-text').textContent || '').trim();
if (!key) return;
let ok = false;
try {
await navigator.clipboard.writeText(key);
ok = true;
} catch (e) { /* 降级 */ }
if (!ok) {
try {
const ta = document.createElement('textarea');
ta.value = key;
ta.style.position = 'fixed';
ta.style.opacity = '0';
document.body.appendChild(ta);
ta.select();
ok = document.execCommand('copy');
document.body.removeChild(ta);
} catch (e2) {
ok = false;
}
}
$('cb-copy-tip').textContent = ok ? '已复制,可直接粘贴到侧边栏激活' : '复制失败,请手动长按选择卡密';
});
loadPackages();