Files
wordpress-theme-aksrt/assets/js/cms-download.js
T
kate522andClaude Fable 5 1df2822f01 feat(pay): 统一收银台四种模式(站内二维码/当前页跳转/表单提交/JSAPI)
- PaymentOrchestrator 统一返回 mode + code_url/payment_url/form_html/jsapi_params
- 支付宝:当面付二维码 / 电脑网站支付 / H5;微信:Native / H5 / JSAPI(WechatOpenid 静默授权)
- 易支付:PC mapi 二维码,移动端自动提交表单
- 删除旧键 alipay_payment_type / wechat_payment_type / epay_device 与 redirect/qr_content 字段
- 新增 pay-cashier.js,移除 window.open,订单状态接口改为 /pay/order/{n}
- 拆分 credits.js(affiliate / withdraw-history)至 1000 行以内

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-29 14:01:28 +08:00

739 lines
29 KiB
JavaScript
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.
/**
* AKSRT CMS Download
*
* 功能:
* - 监听购买入口(data-aksrt-pay-content / data-action="aksrt-need-purchase")
* - 内容购买收银台、扫码支付与错误提示弹窗
* - 下载已改为直连:按钮为源地址锚点,不再经过本站中转请求
*
* 弹窗视觉走主题 design token(见 assets/css/frontend/cms-download.css),
* 付费收银台沿用参考实现的 colorful header 结构。
*
* 依赖:
* - window.aksrtData.{ ajaxUrl, nonce, siteUrl }
* - 主题已加载 fetch (现代浏览器)
*/
(function () {
'use strict';
if (!window.aksrtData) return;
const REST_BASE = window.aksrtData.siteUrl ? window.aksrtData.siteUrl.replace(/\/$/, '') + '/wp-json/aksrt/v1' : '/wp-json/aksrt/v1';
const PAY_CONTENT_PATH = '/pay/content/';
function escapeHtml(value) {
return String(value == null ? '' : value)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
function escapeAttr(value) {
return escapeHtml(value);
}
// ===== Focus trap state =====
let lastFocusedElement = null;
function getFocusableElements(container) {
if (!container) return [];
return Array.prototype.slice.call(container.querySelectorAll(
'a[href], area[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]),' +
' select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
)).filter(function (el) {
return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
});
}
function trapFocus(e, modal) {
if (e.key !== 'Tab') return;
const focusables = getFocusableElements(modal);
if (!focusables.length) {
e.preventDefault();
return;
}
const first = focusables[0];
const last = focusables[focusables.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault();
last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault();
first.focus();
}
}
// ===== Modal helper =====
function buildModal() {
let modal = document.getElementById('aksrt-dl-modal');
if (modal) return modal;
modal = document.createElement('div');
modal.id = 'aksrt-dl-modal';
modal.className = 'aksrt-dl-modal';
modal.setAttribute('role', 'dialog');
modal.setAttribute('aria-modal', 'true');
modal.setAttribute('aria-hidden', 'true');
modal.setAttribute('aria-labelledby', 'aksrt-dl-modal-title');
modal.innerHTML =
'<div class="aksrt-dl-modal__backdrop" data-close></div>' +
'<div class="aksrt-dl-modal__dialog modal-mini">' +
' <div class="aksrt-dl-modal__panel modal-content" role="document" tabindex="-1">' +
' <button type="button" class="aksrt-dl-modal__close" aria-label="关闭" data-close>×</button>' +
' <div class="aksrt-dl-modal__body" data-body></div>' +
' </div>' +
'</div>';
document.body.appendChild(modal);
modal.addEventListener('click', function (e) {
if (e.target.closest('[data-close]')) close();
});
modal.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && modal.classList.contains('is-open')) {
e.preventDefault();
close();
} else if (e.key === 'Tab' && modal.classList.contains('is-open')) {
trapFocus(e, modal);
}
});
return modal;
}
function open(html, returnFocusEl, variant) {
// 记录调用方按钮以便关闭后回焦
lastFocusedElement = returnFocusEl || document.activeElement;
const modal = buildModal();
const body = modal.querySelector('[data-body]');
modal.classList.toggle('is-cashier', variant === 'cashier');
body.innerHTML = html;
const title = body.querySelector('.aksrt-dl-modal__title, .aksrt-cashier__title');
if (title) title.id = 'aksrt-dl-modal-title';
modal.classList.add('is-open');
modal.setAttribute('aria-hidden', 'false');
document.body.classList.add('aksrt-dl-modal-open');
// 进入后将焦点放在第一个可聚焦元素,否则放 panel
setTimeout(function () {
const focusables = getFocusableElements(modal);
if (focusables.length) {
focusables[0].focus();
} else {
modal.querySelector('.aksrt-dl-modal__panel').focus();
}
}, 0);
}
function close() {
const modal = document.getElementById('aksrt-dl-modal');
if (!modal) return;
modal.classList.remove('is-open');
modal.setAttribute('aria-hidden', 'true');
document.body.classList.remove('aksrt-dl-modal-open');
// 焦点回到触发按钮
if (lastFocusedElement && typeof lastFocusedElement.focus === 'function') {
try { lastFocusedElement.focus(); } catch (_) { /* ignore */ }
}
lastFocusedElement = null;
}
// ===== 统一视图壳(下载 / 错误 / 扫码共用主题化外观) =====
// 头部与按钮统一继承运行时主题色(--focus-color),状态用图标 + 标题区分。
const DL_VIEWS = {
login: { icon: 'fa-user-lock', title: '请先登录' },
purchase: { icon: 'fa-shopping-cart', title: '请先购买' },
vip: { icon: 'fa-gem', title: '会员专属' },
credits: { icon: 'fa-coins', title: '积分不足' },
quota: { icon: 'fa-hourglass-half', title: '今日免费额度已用完' },
captcha: { icon: 'fa-shield-alt', title: '验证失败' },
error: { icon: 'fa-exclamation-circle', title: '下载失败' },
pay: { icon: 'fa-qrcode', title: '扫码支付' },
};
function dlActionButton(action) {
if (!action) return '';
const tag = action.kind === 'link' ? 'a' : 'button';
const cls = 'aksrt-dl-modal__btn' + (action.cls ? ' ' + action.cls : '');
const attrs = [
'class="' + cls + '"',
action.url ? 'href="' + escapeAttr(action.url) + '"' : '',
action.attrs || '',
].filter(Boolean).join(' ');
return '<' + tag + ' ' + attrs + '>' +
'<i class="fa fas ' + (action.icon || 'fa-arrow-circle-right') + '" aria-hidden="true"></i>' +
'<span>' + escapeHtml(action.label) + '</span>' +
'</' + tag + '>';
}
function dlView(viewKey, options) {
const meta = DL_VIEWS[viewKey] || DL_VIEWS.error;
options = options || {};
let html = '<div class="aksrt-dl-view">';
html += '<div class="aksrt-dl-modal__hero">';
html += '<div class="aksrt-dl-modal__hero-icon"><i class="fa fas ' + meta.icon + '" aria-hidden="true"></i></div>';
html += '<h3 class="aksrt-dl-modal__title">' + (options.title || meta.title) + '</h3>';
if (options.subtitle) {
html += '<p class="aksrt-dl-modal__subtitle">' + escapeHtml(options.subtitle) + '</p>';
}
html += '</div>';
html += '<div class="aksrt-dl-modal__content">';
html += options.body || '';
if (options.actions && options.actions.length) {
html += '<div class="aksrt-dl-modal__actions">' +
options.actions.map(dlActionButton).join('') +
'</div>';
}
html += '</div></div>';
return html;
}
// ===== 错误提示渲染 =====
function renderError(msg, data, returnFocusEl, code) {
let viewKey = 'error';
let subtitle = '';
let action = null;
data = data || {};
msg = msg || '';
if (code === 'aksrt_need_login') {
viewKey = 'login';
msg = data.login_url ? '登录后即可继续下载,访客不可使用此资源。' : (msg || '无下载权限。');
if (data.login_url) action = { kind: 'link', url: data.login_url, label: '前往登录', icon: 'fa-sign-in-alt' };
} else if (code === 'aksrt_need_purchase') {
viewKey = 'purchase';
msg = msg || '请先完成购买后再下载该资源。';
if (data.post_id) {
action = { kind: 'button', label: '去购买', icon: 'fa-shopping-cart', attrs: 'type="button" data-aksrt-open-cashier="' + escapeAttr(data.post_id) + '"' };
}
} else if (code === 'aksrt_need_vip') {
viewKey = 'vip';
msg = msg || '该资源仅限会员购买/下载。';
subtitle = '开通会员即可解锁本资源';
if (data.vip_url) action = { kind: 'link', url: data.vip_url, label: '开通会员', icon: 'fa-gem' };
} else if (code === 'aksrt_points_insufficient' || code === 'aksrt_balance_insufficient') {
viewKey = 'credits';
const balance = typeof data.balance === 'number' ? data.balance : 0;
const cost = typeof data.cost === 'number' ? data.cost : 0;
msg = '当前余额 ' + balance + ' 积分,需要 ' + cost + ' 积分。可通过签到、阅读、邀请好友等方式获取积分。';
if (data.credits_url) action = { kind: 'link', url: data.credits_url, label: '我的积分', icon: 'fa-coins' };
} else if (code === 'aksrt_download_limit') {
viewKey = 'quota';
msg = msg || '您今日下载免费资源个数已超限,请明日再下载。';
if (data.allow_limit_over && data.post_id) {
action = { kind: 'button', label: '立即购买', icon: 'fa-shopping-cart', attrs: 'type="button" data-aksrt-open-cashier="' + escapeAttr(data.post_id) + '" data-price-type="download_limit_over"' };
}
} else if (code === 'aksrt_captcha_failed') {
viewKey = 'captcha';
msg = msg || '人机验证失败,请重试。';
} else if (!action && data.login_url) {
action = { kind: 'link', url: data.login_url, label: '前往登录', icon: 'fa-sign-in-alt' };
}
open(
dlView(viewKey, {
subtitle: subtitle,
body: msg ? '<p class="aksrt-dl-modal__msg">' + escapeHtml(msg) + '</p>' : '',
actions: action ? [action] : [],
}),
returnFocusEl
);
const openCashierBtn = document.querySelector('[data-aksrt-open-cashier]');
if (openCashierBtn) {
openCashierBtn.addEventListener('click', function () {
const pid = parseInt(openCashierBtn.getAttribute('data-aksrt-open-cashier'), 10) || 0;
const pt = openCashierBtn.getAttribute('data-price-type') || '';
close();
if (pid) openCashier(pid, returnFocusEl, pt);
});
}
}
function cashierHeader() {
return '<div class="modal-colorful-header colorful-bg jb-blue">' +
'<button type="button" class="close aksrt-cashier__close" aria-label="关闭" data-close>×</button>' +
'<div class="colorful-make"></div>' +
'<div class="text-center">' +
' <div class="em2x"><i class="fa fas fa-cart-plus" aria-hidden="true"></i></div>' +
' <div class="aksrt-cashier__title mt10 em12 padding-w10">确认购买</div>' +
'</div>' +
'</div>';
}
function openCashierContent(content, returnFocusEl) {
open(
cashierHeader() + '<div class="modal-pay-body box-body">' + content + '</div>',
returnFocusEl,
'cashier'
);
}
function pollOrderPaid(orderNumber, returnFocusEl) {
if (!orderNumber) return;
let tries = 0;
const maxTries = 40;
const timer = setInterval(function () {
tries += 1;
fetch(REST_BASE + '/pay/order/' + encodeURIComponent(orderNumber), {
method: 'GET',
credentials: 'same-origin',
headers: restHeaders(),
})
.then(function (resp) { return resp.json().then(function (j) { return { ok: resp.ok, json: j }; }); })
.then(function (r) {
if (r.ok && r.json && r.json.paid) {
clearInterval(timer);
window.location.reload();
}
})
.catch(function () { /* ignore */ });
if (tries >= maxTries) clearInterval(timer);
}, 3000);
}
// ===== 收银台(内容购买) =====
function restHeaders(idempotencyKey) {
const headers = {
'Content-Type': 'application/json',
'X-WP-Nonce': window.aksrtData.restNonce || '',
};
if (idempotencyKey) headers['Idempotency-Key'] = idempotencyKey;
return headers;
}
function buildPreviewUrl(postId, priceType, couponCode) {
var previewUrl = REST_BASE + PAY_CONTENT_PATH + postId;
var qs = [];
if (priceType) qs.push('price_type=' + encodeURIComponent(priceType));
if (couponCode) qs.push('coupon_code=' + encodeURIComponent(couponCode));
if (qs.length) previewUrl += '?' + qs.join('&');
return previewUrl;
}
function fetchPreview(postId, priceType, couponCode) {
return fetch(buildPreviewUrl(postId, priceType, couponCode), {
method: 'GET',
credentials: 'same-origin',
headers: restHeaders(),
}).then(function (resp) {
return resp.json().then(function (j) { return { ok: resp.ok, status: resp.status, json: j }; });
});
}
function openCashier(postId, returnFocusEl, priceType, couponCode) {
if (!postId) return;
priceType = priceType || '';
couponCode = couponCode || '';
openCashierContent(
'<p class="aksrt-cashier__loading muted-2-color">加载中…</p>',
returnFocusEl
);
fetchPreview(postId, priceType, couponCode)
.then(function (r) {
if (!r.ok || (r.json && r.json.code)) {
const code = r.json && r.json.code ? r.json.code : '';
const data = (r.json && r.json.data) || {};
if (code === 'aksrt_need_login' || r.status === 401) {
renderError(r.json.message || '', data, returnFocusEl, 'aksrt_need_login');
return;
}
if (code === 'aksrt_need_vip') {
renderError(r.json.message || '', data, returnFocusEl, 'aksrt_need_vip');
return;
}
if (code === 'aksrt_already_paid') {
window.location.reload();
return;
}
renderError((r.json && r.json.message) || '请稍后重试', data, returnFocusEl, code);
return;
}
var prev = r.json || {};
if (priceType) prev._price_type = priceType;
if (couponCode) prev._coupon_code = couponCode;
renderCashier(prev, returnFocusEl);
})
.catch(function () {
renderError('网络错误,请稍后重试', {}, returnFocusEl);
});
}
function formatAssetLabel(preview, value) {
const currency = escapeHtml(preview.currency || (preview.pay_modo === 'points' ? '积分' : '¥'));
const amount = escapeHtml(value == null ? '' : value);
return preview.pay_modo === 'points' ? amount + currency : currency + amount;
}
function paymentMethodLogo(methodId) {
const logoNames = {
balance: 'pay-balance-logo.svg',
alipay: 'pay-alipay-logo.svg',
epay_alipay: 'pay-alipay-logo.svg',
wechatpay: 'pay-wechat-logo.svg',
epay_wxpay: 'pay-wechat-logo.svg',
paypal: 'pay-paypal-logo.svg',
stripe: 'pay-stripe-logo.svg',
};
const logo = logoNames[methodId];
if (!logo) return '';
const themeUrl = String(window.aksrtData.themeUrl || '').replace(/\/$/, '');
return '<img src="' + escapeAttr(themeUrl + '/assets/img/' + logo) + '" alt="">';
}
function cashierPriceRow(label, value, className) {
return '<div class="flex jsb ab aksrt-cashier__price-row">' +
'<span class="muted-2-color">' + label + '</span>' +
'<div class="' + (className || '') + '">' + value + '</div>' +
'</div>';
}
function renderCashier(preview, returnFocusEl) {
if (!preview) return;
if (preview.need_login && !preview.allow_guest) {
renderError('', { login_url: preview.login_url }, returnFocusEl, 'aksrt_need_login');
return;
}
if (preview.need_vip) {
renderError('', { vip_url: preview.vip_url }, returnFocusEl, 'aksrt_need_vip');
return;
}
if (preview.has_paid_access) {
window.location.reload();
return;
}
// Content free: reload.
if (preview.is_free) {
window.location.reload();
return;
}
const isPoints = preview.pay_modo === 'points';
const priceType = (preview._price_type || preview.price_type || '');
let amountLabel = escapeHtml(preview.amount_label || '');
let baseLabel = escapeHtml(preview.base_label || '');
const listLabel = escapeHtml(preview.list_amount_label || preview.amount_label || amountLabel);
if (priceType === 'download_limit_over' && preview.download_limit_over_label && !preview.discounts) {
amountLabel = escapeHtml(preview.download_limit_over_label);
baseLabel = amountLabel;
preview.amount = preview.download_limit_over_price;
preview.base = preview.download_limit_over_price;
}
const title = escapeHtml(preview.title || '');
const typeLabel = escapeHtml(preview.pay_type_label || '');
const balance = isPoints && Number.isSafeInteger(preview.balance) ? preview.balance : 0;
const methods = Array.isArray(preview.payment_methods) ? preview.payment_methods : [];
const couponAllowed = !isPoints && !!preview.coupon_allowed;
const couponCodeVal = escapeAttr(preview.coupon_code || preview._coupon_code || '');
let priceRows = '';
if (!preview.pay_limit) {
let regularPrice = '<span class="em14">' + baseLabel + '</span>';
if (!isPoints && Number(preview.original) > Number(preview.base)) {
regularPrice = '<span class="original-price">' + formatAssetLabel(preview, preview.original) + '</span>' + regularPrice;
}
priceRows += cashierPriceRow('价格', regularPrice, 'aksrt-cashier__regular-price');
}
if (preview.list_amount !== preview.base) {
priceRows += cashierPriceRow(
'会员价<span class="aksrt-cashier__vip-mark">VIP' + escapeHtml(preview.vip_level || '') + '</span>',
'<span class="em14">' + listLabel + '</span>',
'c-red'
);
} else if (preview.amount !== preview.base && !preview.rebate_discount && !preview.coupon_discount) {
priceRows += cashierPriceRow(
'会员价<span class="aksrt-cashier__vip-mark">VIP' + escapeHtml(preview.vip_level || '') + '</span>',
'<span class="em14">' + amountLabel + '</span>',
'c-red'
);
}
if (preview.rebate_discount > 0) {
priceRows += cashierPriceRow(
'推广折扣',
'<span class="em14">' + escapeHtml(preview.rebate_discount_label || ('-' + preview.rebate_discount)) + '</span>'
);
}
if (preview.coupon_discount > 0) {
priceRows += cashierPriceRow(
'优惠码',
'<span class="em14">' + escapeHtml(preview.coupon_discount_label || ('-' + preview.coupon_discount)) + '</span>'
);
}
if (preview.amount !== preview.base || preview.rebate_discount > 0 || preview.coupon_discount > 0) {
priceRows += cashierPriceRow(
'支付金额',
'<span class="em14">' + amountLabel + '</span>',
'font-bold c-red'
);
}
let body = '';
body += '<div class="mb10 order-type-' + escapeAttr(preview.pay_type || '') + '">' +
'<span class="pay-tag badg badg-sm mr6">' + typeLabel + '</span><span>' + title + '</span>' +
'</div>';
body += '<div class="mb10 muted-box padding-h6 line-16">' + priceRows + '</div>';
if (isPoints) {
body += '<div class="mb10 muted-box padding-h10">' +
'<div class="flex jsb ac"><span class="muted-2-color">' +
'<i class="fa fas fa-database aksrt-cashier__asset-icon" aria-hidden="true"></i>我的积分' +
'</span><div class="c-green"><span class="em14">' + balance + '</span></div></div></div>';
if (!preview.balance_enough) {
body += '<div class="badg c-red btn-block mb20 aksrt-cashier__warn">抱歉,您的积分不足,暂时无法支付</div>';
body += '<div class="modal-buts but-average">';
if (preview.credits_url) {
body += '<a class="but padding-lg" href="' + escapeAttr(preview.credits_url) + '">我的积分</a>';
}
body += '</div>';
openCashierContent(body, returnFocusEl);
return;
}
body += '<button type="button" class="mt6 but jb-red initiate-pay btn-block radius" data-aksrt-pay-submit data-mode="points" data-price-type="' + escapeAttr(priceType) + '">' +
'立即支付<span class="pay-price-text ml10">' + amountLabel + '</span></button>';
} else {
if (couponAllowed) {
body += '<div class="mb10 coupon-input-box">';
body += '<div class="muted-2-color em09 mb6">使用优惠码</div>';
body += '<div class="relative">';
body += '<input type="text" class="form-control coupon-input" name="aksrt_coupon_code" value="' + couponCodeVal + '" placeholder="请输入优惠码" autocomplete="off">';
body += '<span class="abs-right"><button type="button" class="but c-blue coupon-submit" data-aksrt-check-coupon>检查优惠码</button></span>';
body += '</div>';
if (preview.coupon_desc) {
body += '<div class="muted-3-color px12 mt6 aksrt-cashier__coupon-desc">' + escapeHtml(preview.coupon_desc) + '</div>';
}
if (preview.coupon_error) {
body += '<div class="aksrt-cashier__warn">' + escapeHtml(preview.coupon_error) + '</div>';
} else if (preview.coupon_discount > 0) {
body += '<div class="aksrt-cashier__coupon-ok">优惠码可用</div>';
}
body += '</div>';
}
const walletBal = typeof preview.wallet_balance === 'string'
? preview.wallet_balance
: (typeof preview.balance === 'string' ? preview.balance : '0.00');
const hasBalanceMethod = methods.some(function (m) { return (m && m.id) === 'balance'; });
let methodHtml = '';
methods.forEach(function (m, idx) {
const rawId = String((m && m.id) || '');
const id = escapeAttr(rawId);
const name = escapeHtml((m && m.name) || rawId);
methodHtml += '<div class="flex jc hh payment-method-radio hollow-radio flex-auto pointer' + (idx === 0 ? ' active' : '') + '"' +
' role="radio" aria-checked="' + (idx === 0 ? 'true' : 'false') + '" tabindex="0" data-aksrt-payment-method="' + id + '">' +
paymentMethodLogo(rawId) + '<div>' + name + '</div></div>';
});
if (methods.length > 1) {
body += '<div class="muted-2-color em09 mb6">请选择支付方式</div>' +
'<div class="flex mb10 aksrt-cashier__methods" role="radiogroup">' + methodHtml + '</div>';
}
if (methods.length) {
body += '<input type="hidden" name="aksrt_pay_method" value="' + escapeAttr(methods[0].id || '') + '">';
}
if (hasBalanceMethod) {
const balanceSelected = methods[0] && methods[0].id === 'balance';
body += '<div class="mb10 muted-box padding-h10 aksrt-cashier__balance" data-aksrt-balance-box' + (balanceSelected ? '' : ' hidden') + '>' +
'<div class="flex jsb ac"><span class="muted-2-color">' +
'<i class="fa fas fa-wallet aksrt-cashier__asset-icon" aria-hidden="true"></i>我的余额' +
'</span><div class="c-blue-2"><span class="em14">' + escapeHtml(walletBal) + '</span></div></div>';
if (preview.balance_enough === false) {
if (preview.credits_url) {
body += '<a class="but c-red block mt6 aksrt-cashier__recharge" href="' + escapeAttr(preview.credits_url) + '">' +
'抱歉!余额不足,请先充值 <i class="ml6 fa fas fa-angle-right em12" aria-hidden="true"></i></a>';
} else {
body += '<div class="c-red mt6 aksrt-cashier__recharge">抱歉!余额不足,请先充值</div>';
}
}
body += '</div>';
}
if (methods.length) {
body += '<button type="button" class="mt6 but jb-red initiate-pay btn-block radius" data-aksrt-pay-submit data-mode="cash" data-price-type="' + escapeAttr(priceType) + '">' +
'立即支付<span class="pay-price-text ml10">' + amountLabel + '</span></button>';
} else {
body += '<div class="badg c-red btn-block aksrt-cashier__warn">暂无可用支付方式,请联系站长配置收款接口</div>';
}
}
openCashierContent(body, returnFocusEl);
const modal = document.getElementById('aksrt-dl-modal');
const checkBtn = modal && modal.querySelector('[data-aksrt-check-coupon]');
if (checkBtn) {
checkBtn.addEventListener('click', function () {
const input = modal.querySelector('input[name="aksrt_coupon_code"]');
const code = input ? String(input.value || '').trim() : '';
if (!code) {
if (input) input.focus();
return;
}
checkBtn.disabled = true;
const old = checkBtn.innerHTML;
checkBtn.innerHTML = '检查中…';
fetchPreview(preview.post_id, priceType, code)
.then(function (r) {
checkBtn.disabled = false;
checkBtn.innerHTML = old;
if (!r.ok || (r.json && r.json.code && !r.json.amount && r.json.amount !== 0)) {
renderError((r.json && r.json.message) || '请稍后重试', (r.json && r.json.data) || {}, returnFocusEl, (r.json && r.json.code) || '');
return;
}
var prev = r.json || {};
if (priceType) prev._price_type = priceType;
prev._coupon_code = code;
renderCashier(prev, returnFocusEl);
})
.catch(function () {
checkBtn.disabled = false;
checkBtn.innerHTML = old;
renderError('网络错误,请稍后重试', {}, returnFocusEl);
});
});
}
if (modal) {
const methodCards = modal.querySelectorAll('[data-aksrt-payment-method]');
const methodInput = modal.querySelector('input[name="aksrt_pay_method"]');
const balanceBox = modal.querySelector('[data-aksrt-balance-box]');
const selectMethod = function (card) {
const method = card.getAttribute('data-aksrt-payment-method') || '';
methodCards.forEach(function (item) {
const active = item === card;
item.classList.toggle('active', active);
item.setAttribute('aria-checked', active ? 'true' : 'false');
});
if (methodInput) methodInput.value = method;
if (balanceBox) balanceBox.hidden = method !== 'balance';
};
methodCards.forEach(function (card) {
card.addEventListener('click', function () { selectMethod(card); });
card.addEventListener('keydown', function (event) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
selectMethod(card);
}
});
});
}
const submitBtn = modal && modal.querySelector('[data-aksrt-pay-submit]');
if (submitBtn) {
submitBtn.addEventListener('click', function () {
const mode = submitBtn.getAttribute('data-mode') || (isPoints ? 'points' : 'cash');
let paymentMethod = 'balance';
const selected = modal.querySelector('input[name="aksrt_pay_method"]');
if (selected) paymentMethod = selected.value;
const input = modal.querySelector('input[name="aksrt_coupon_code"]');
const code = input ? String(input.value || '').trim() : (preview.coupon_code || preview._coupon_code || '');
submitPurchase(preview.post_id, mode, paymentMethod, submitBtn, returnFocusEl, code);
});
}
}
function submitPurchase(postId, mode, paymentMethod, button, returnFocusEl, couponCode) {
if (!postId) return;
const original = button.innerHTML;
button.disabled = true;
button.innerHTML = '处理中…';
const payload = { mode: mode };
if (mode === 'cash') payload.payment_method = paymentMethod || 'balance';
const pt = button.getAttribute('data-price-type') || '';
if (pt) payload.price_type = pt;
if (mode === 'cash' && couponCode) payload.coupon_code = couponCode;
if (!button.dataset.aksrtOperationKey) {
button.dataset.aksrtOperationKey = window.AksrtRequest.createIdempotencyKey();
}
fetch(REST_BASE + PAY_CONTENT_PATH + postId, {
method: 'POST',
credentials: 'same-origin',
headers: restHeaders(button.dataset.aksrtOperationKey),
body: JSON.stringify(payload),
})
.then(function (resp) { return resp.json().then(function (j) { return { ok: resp.ok, status: resp.status, json: j }; }); })
.then(function (r) {
button.disabled = false;
button.innerHTML = original;
if (!r.ok || (r.json && r.json.code && !r.json.success)) {
const code = r.json && r.json.code ? r.json.code : '';
const data = (r.json && r.json.data) || {};
if (code === 'aksrt_already_paid') {
window.location.reload();
return;
}
renderError((r.json && r.json.message) || '请稍后重试', data, returnFocusEl, code);
return;
}
const data = (r.json && r.json.data) ? r.json.data : r.json;
delete button.dataset.aksrtOperationKey;
if (data && data.mode) {
if (window.AksrtModalRuntime) window.AksrtModalRuntime.invalidate(['orders']);
if (data.mode === 'qrcode') {
renderScanPay(data, returnFocusEl);
} else if (window.AksrtPayCashier) {
close();
window.AksrtPayCashier.handle(data, {});
}
if ((data.mode === 'qrcode' || data.mode === 'jsapi') && data.order_number) {
pollOrderPaid(data.order_number, returnFocusEl);
}
return;
}
window.location.reload();
})
.catch(function () {
button.disabled = false;
button.innerHTML = original;
renderError('网络错误,请稍后重试', {}, returnFocusEl);
});
}
function renderScanPay(data, returnFocusEl) {
data = data || {};
const body = window.AksrtPayCashier
? window.AksrtPayCashier.qrHtml(data)
: '<p class="aksrt-dl-modal__msg">' + escapeHtml('收银台组件未加载,请刷新页面重试。') + '</p>';
open(
dlView('pay', {
subtitle: '支付完成后页面会自动刷新',
body: body,
actions: [{ kind: 'button', label: '我已支付', icon: 'fa-sync-alt', attrs: 'type="button" data-aksrt-reload' }],
}),
returnFocusEl
);
const reloadBtn = document.querySelector('[data-aksrt-reload]');
if (reloadBtn) reloadBtn.addEventListener('click', function () { window.location.reload(); });
}
// ===== 事件绑定 =====
document.addEventListener('click', function (e) {
const buyBtn = e.target.closest('[data-action="aksrt-need-purchase"], [data-aksrt-pay-content]');
if (buyBtn) {
e.preventDefault();
const postId = parseInt(
buyBtn.getAttribute('data-aksrt-pay-content') ||
buyBtn.getAttribute('data-post-id') ||
(buyBtn.closest('[data-post-id]') && buyBtn.closest('[data-post-id]').getAttribute('data-post-id')) ||
'0',
10
);
const priceType = buyBtn.getAttribute('data-price-type') || '';
if (postId) openCashier(postId, buyBtn, priceType);
return;
}
});
// Public cashier bridge (content pay).
window.aksrtOpenCashier = function (postId, returnFocusEl, priceType, couponCode) {
openCashier(postId, returnFocusEl || null, priceType || '', couponCode || '');
};
})();