Files
jiu/web/checkout.njk
T
wangjia c010d8b416 fix(site): 官网 401 自动用 refresh_token 续签重试;资产加构建版本号破缓存
- 新增共享 auth.js(jiuAuth):access token 60 分钟过期后,checkout 提交/
  结果页轮询遇 401 自动 POST /auth/refresh 换新 token 并重试一次,仍失败
  才跳登录——修复「已登录点提交订单却被踢回登录页」
- 登录判定放宽为 access 或 refresh token 任一存在(refresh 7 天有效)
- 所有 JS/CSS 引用加 ?v=构建版本号:修复旧版 nav.js 被浏览器缓存后在新
  页面执行(右上角错位的旧用户面板、带引号的 "张三" 显示)
- nav.js 改经 jiuAuth 读 localStorage(JSON 解码,去掉引号显示)
- 登录页回填上次的门店编号与账号

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
2026-07-03 21:08:13 +08:00

145 lines
6.7 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
layout: base.njk
title: 确认订单
description: 选择套餐与计费周期,在线支付开通或续费门店授权。
permalink: /checkout/
---
{# 官网内购买/续费(需登录):套餐+周期联动汇总 → POST /api/v1/license/purchase
拿 pay_url 跳收银台(契约 pay-contract v1.0.0)。版式仿原型 screens/checkout.html。 #}
<div class="container" style="padding:40px 24px 72px">
<div class="crumb"><a href="/#pricing">购买授权</a><span>/</span><span>确认订单</span></div>
<h1 class="co-title">确认订单</h1>
<div class="checkout">
<div>
<div class="co-card">
<h2>选择套餐</h2>
<div class="seg" id="planSeg">
<button class="seg-btn on" data-v="standard">标准版</button>
<button class="seg-btn" data-v="pro">高级版</button>
</div>
<ul class="co-feats" id="planFeats"></ul>
<div class="seg-label">计费周期</div>
<div class="seg" id="cycleSeg">
<button class="seg-btn on" data-v="annual">年付 <span class="seg-tip">省两个月</span></button>
<button class="seg-btn" data-v="monthly">月付</button>
</div>
</div>
<div class="co-card">
<h2>订单内容</h2>
<div class="co-item">
<div class="co-ic"><svg class="ic"><use href="#i-ic07"/></svg></div>
<div class="co-meta"><b id="itName">标准版授权</b><span id="itDur">365 天授权 · 到期时间自动叠加</span></div>
<div class="co-amt" id="itAmt">¥2,999</div>
</div>
</div>
<div class="co-card">
<h2>付款方式</h2>
<div class="co-pay">
<div class="pm on">
<div class="pic" style="background:#1677FF"><svg class="ic"><use href="#i-ic08"/></svg></div>
<div class="nm"><b>支付宝</b><span>电脑扫码 / 手机拉起支付宝,即时到账</span></div>
<div class="radio"></div>
</div>
<div class="pm off">
<div class="pic" style="background:#8A94A6"><svg class="ic"><use href="#i-ic11"/></svg></div>
<div class="nm"><b>对公转账</b><span>需要合同与对公收款请<a href="mailto:{{ site.support.email }}">联系我们</a></span></div>
</div>
</div>
</div>
</div>
<div class="co-card co-summary">
<h2>费用汇总</h2>
<div class="sum-row"><span id="sumName">标准版 · 年付</span><span id="sumBase">¥2,999</span></div>
<div class="sum-row muted"><span>优惠</span><span> ¥0</span></div>
<div class="sum-div"></div>
<div class="sum-total"><span class="lab">应付总额</span><span class="val" id="sumTotal">¥2,999</span></div>
<div class="auth-alert" id="coErr" style="margin-top:12px"></div>
<button class="btn primary lg" id="coBtn" style="width:100%;margin-top:16px">提交订单</button>
<div class="fine">提交后跳转支付宝收银台,支付成功即时续期到当前门店。时长在现有到期时间上叠加,不覆盖。</div>
</div>
</div>
</div>
<script>
(function () {
var API = '{{ site.appBaseUrl }}';
function $(id) { return document.getElementById(id); }
function gotoLogin() { location.href = '/login/?next=' + encodeURIComponent('/checkout/' + location.search); }
// access token 只有 60 分钟,只要 refresh token(7 天)还在就算登录,请求时自动续签
if (!jiuAuth.loggedIn()) { gotoLogin(); return; }
/* 套餐目录:价格仅前端展示,实付金额以 pay 套餐表为准(下单响应回传) */
var PLANS = {
standard: {
name: '标准版',
feats: ['单门店 · 单仓库', '2 台客户端同时使用', '1,000 张商品图片分享'],
monthly: { biz: 'monthly_standard', price: 299 },
annual: { biz: 'annual_standard', price: 2999 },
},
pro: {
name: '高级版',
feats: ['单门店 · 多仓库', '5 台客户端同时使用', '10,000 张商品图片分享', '免费 AI 周度 / 月度商业数据分析'],
monthly: { biz: 'monthly_pro', price: 599 },
annual: { biz: 'annual_pro', price: 5999 },
},
};
var q = new URLSearchParams(location.search);
var plan = PLANS[q.get('plan')] ? q.get('plan') : 'standard';
var cycle = 'annual';
function fmt(n) { return '¥' + n.toLocaleString('zh-CN'); }
function render() {
var p = PLANS[plan], c = p[cycle];
var days = cycle === 'annual' ? 365 : 30;
$('planFeats').innerHTML = p.feats.map(function (f) {
return '<li><svg class="ic"><use href="#i-check"/></svg>' + f + '</li>';
}).join('');
$('itName').textContent = p.name + '授权 · ' + (cycle === 'annual' ? '年付' : '月付');
$('itDur').textContent = days + ' 天授权 · 到期时间自动叠加';
$('itAmt').textContent = fmt(c.price);
$('sumName').textContent = p.name + ' · ' + (cycle === 'annual' ? '年付' : '月付');
$('sumBase').textContent = fmt(c.price);
$('sumTotal').textContent = fmt(c.price);
document.querySelectorAll('#planSeg .seg-btn').forEach(function (b) { b.classList.toggle('on', b.dataset.v === plan); });
document.querySelectorAll('#cycleSeg .seg-btn').forEach(function (b) { b.classList.toggle('on', b.dataset.v === cycle); });
}
document.querySelectorAll('#planSeg .seg-btn').forEach(function (b) {
b.addEventListener('click', function () { plan = b.dataset.v; render(); });
});
document.querySelectorAll('#cycleSeg .seg-btn').forEach(function (b) {
b.addEventListener('click', function () { cycle = b.dataset.v; render(); });
});
render();
function showErr(msg) { var el = $('coErr'); el.textContent = msg; el.classList.add('show'); }
$('coBtn').addEventListener('click', function () {
$('coErr').classList.remove('show');
var btn = $('coBtn'); btn.disabled = true; btn.textContent = '正在创建订单…';
jiuAuth.authFetch(API, '/api/v1/license/purchase', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ biz_code: PLANS[plan][cycle].biz }),
})
.then(function (r) {
// authFetch 已自动用 refresh_token 续签重试过,仍 401 才是真过期
if (r.status === 401) { gotoLogin(); throw new Error('登录已过期'); }
return r.json().then(function (j) { return { ok: r.ok, j: j }; });
})
.then(function (res) {
if (!res.ok) throw new Error((res.j && res.j.error) || '下单失败,请稍后重试');
var d = res.j.data || {};
if (!d.pay_url) throw new Error('下单响应异常');
location.href = d.pay_url;
})
.catch(function (e) {
showErr(e && e.message ? e.message : '无法连接服务器,请稍后重试');
btn.disabled = false; btn.textContent = '提交订单';
});
});
})();
</script>