购买页轮询到服务端发卡即渲染卡密框:等宽大字 + 一键复制(clipboard API + execCommand 降级)。自动激活成功显示「已自动激活本机」;失败显示「自动激活未生效」并指引粘贴卡密到侧边栏手动激活——付了钱拿不到卡密、只能找客服的情况不再出现。
171 lines
7.0 KiB
JavaScript
171 lines
7.0 KiB
JavaScript
/* 购买授权页:选套餐 → 选支付方式 → 下单 → 支付 → 自动激活 */
|
||
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) => ({
|
||
'&': '&', '<': '<', '>': '>', '"': '"', "'": '''
|
||
}[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;
|
||
const priceText = res.order.couponCode
|
||
? `¥${res.order.amount}(原价 ¥${res.order.originalPrice},已用优惠码 ${res.order.couponCode})`
|
||
: `¥${res.order.amount}`;
|
||
status.textContent = `订单 ${orderNo}(${priceText})已创建,正在打开支付页面…`;
|
||
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},如已付款请把订单号发给客服QQ 1556688283 手动补发。`;
|
||
return;
|
||
}
|
||
try {
|
||
const res = await chrome.runtime.sendMessage({ action: 'authQueryOrder', orderNo });
|
||
if (!res || !res.ok) return;
|
||
if (res.cardKey) {
|
||
// 只要服务端已发卡就把卡密显示出来——哪怕自动激活失败,用户也能自己复制激活
|
||
clearInterval(pollTimer);
|
||
pollTimer = null;
|
||
showCard(res);
|
||
} else {
|
||
const status = $('order-status');
|
||
status.className = 'order-status pending';
|
||
status.textContent = `⏳ 等待支付中…(订单 ${orderNo},已等待 ${attempts * 5} 秒)`;
|
||
}
|
||
} catch (e) {
|
||
/* 单次查询失败不中断轮询 */
|
||
}
|
||
}, 5000);
|
||
}
|
||
|
||
// 支付成功:显示卡密(激活成功/失败都显示,激活失败时给出手动激活指引)
|
||
function showCard(res) {
|
||
const status = $('order-status');
|
||
const ok = !!res.activated;
|
||
status.className = 'order-status ' + (ok ? 'success' : 'error');
|
||
status.innerHTML = ok
|
||
? `<div class="ok-title">✓ 支付成功,已自动激活本机</div>
|
||
<div class="ok-line">到期时间:${esc(res.expireTime || '永久')}</div>
|
||
<div class="ok-line muted">回到插件侧边栏即可使用全部功能。</div>`
|
||
: `<div class="ok-title">✓ 支付成功</div>
|
||
<div class="ok-line warn">自动激活未生效${(res.licenseState && res.licenseState.message) ? ':' + esc(res.licenseState.message) : ''}</div>
|
||
<div class="ok-line muted">请复制下面的卡密,回到插件侧边栏「输入卡密」框粘贴 → 点「激活」即可。</div>`;
|
||
|
||
$('card-key').textContent = res.cardKey || '';
|
||
$('card-hint').textContent = ok
|
||
? '卡密已随本机自动激活,无需手动输入;保存好可换机或重装后使用。'
|
||
: '本页不会关闭也不会失效,卡密就在这里,随时可以复制。';
|
||
$('card-box').hidden = false;
|
||
$('card-box').scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||
}
|
||
|
||
// 复制卡密(剪贴板 API 失败时降级 execCommand)
|
||
$('copy-card').addEventListener('click', async () => {
|
||
const key = ($('card-key').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.cssText = 'position:fixed;left:-9999px';
|
||
document.body.appendChild(ta);
|
||
ta.select();
|
||
ok = document.execCommand('copy');
|
||
ta.remove();
|
||
} catch (e) { ok = false; }
|
||
}
|
||
$('copy-card').textContent = ok ? '✓ 已复制' : '复制失败,请手动选中卡密复制';
|
||
setTimeout(() => { $('copy-card').textContent = '复制卡密'; }, 2500);
|
||
});
|
||
|
||
document.querySelectorAll('.pay-btn').forEach((btn) => {
|
||
btn.addEventListener('click', () => createOrder(btn.dataset.pay));
|
||
});
|
||
|
||
loadPackages();
|