/**
* AKSRT CMS Download
*
* 功能:
* - 监听 [data-action="aksrt-download"] 按钮点击
* - 调 POST /aksrt/v1/download/request
* - 弹窗显示下载链接(含提取码)
*
* 弹窗视觉走主题 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, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
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 =
'
' +
'';
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 = {
success: { icon: 'fa-cloud-download-alt', title: '下载已就绪' },
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 + '>' +
'' +
'' + escapeHtml(action.label) + '' +
'' + tag + '>';
}
function dlView(viewKey, options) {
const meta = DL_VIEWS[viewKey] || DL_VIEWS.error;
options = options || {};
let html = '';
html += '
';
html += '
';
html += '
' + (options.title || meta.title) + '
';
if (options.subtitle) {
html += '
' + escapeHtml(options.subtitle) + '
';
}
html += '
';
html += '
';
html += options.body || '';
if (options.actions && options.actions.length) {
html += '
' +
options.actions.map(dlActionButton).join('') +
'
';
}
html += '
';
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 ? '' + escapeHtml(msg) + '
' : '',
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 renderSuccess(payload, returnFocusEl) {
payload = payload || {};
const code = String(payload.extract_code || '').trim();
const has = !!payload.has_extract || code !== '';
const safeCode = escapeHtml(code);
const safeCodeAttr = escapeAttr(code);
const expiresIn = typeof payload.expires_in === 'number' && payload.expires_in > 0
? Math.max(1, Math.round(payload.expires_in / 60))
: 10;
const hintMsg = '点击下方按钮开始下载,链接 ' + expiresIn + ' 分钟内有效。';
let body = '';
if (code !== '') {
body +=
'' +
'
网盘提取码
' +
'
' +
' ' + safeCode + '' +
' ' +
'
' +
'
' +
'' + escapeHtml(hintMsg) + '
';
} else if (has) {
body += '下载源标记了提取码,但接口未返回具体内容,请检查资源下载源的 extract_code 字段。
';
} else {
body += '' + escapeHtml(hintMsg) + '
';
}
const chips = [];
const sourceLabel = String(payload.source_label || '').trim();
if (sourceLabel !== '') {
chips.push('' + escapeHtml(sourceLabel) + '');
}
if (chips.length) {
body += '' + chips.join('') + '
';
}
open(
dlView('success', {
subtitle: '下载链接已生成,请及时将文件保存到本地。',
body: body,
actions: [{
kind: 'link',
url: payload.dl_url || '#',
label: '立即下载',
icon: 'fa-download',
cls: 'aksrt-dl-modal__download',
attrs: 'target="_blank" rel="noopener noreferrer"',
}],
}),
returnFocusEl
);
// 复制按钮
document.querySelectorAll('[data-copy]').forEach(function (btn) {
btn.addEventListener('click', function () {
const text = btn.getAttribute('data-copy') || '';
const label = btn.querySelector('.aksrt-dl-modal__copy-text') || btn;
if (navigator.clipboard) {
navigator.clipboard.writeText(text).then(function () {
label.textContent = '已复制';
btn.classList.add('is-done');
setTimeout(function () {
label.textContent = '复制';
btn.classList.remove('is-done');
}, 2000);
});
}
});
});
}
// ===== 主流程 =====
function captchaScope(sourceEl) {
const box = sourceEl ? sourceEl.closest('[data-aksrt-download-box]') : null;
return box ? (box.querySelector('[data-aksrt-download-captcha]') || box) : sourceEl;
}
function isCaptchaActive(sourceEl) {
const card = sourceEl ? sourceEl.closest('[data-aksrt-download-box]') : null;
if (!card) return false;
return card.getAttribute('data-captcha') === '1';
}
function cashierHeader() {
return '';
}
function openCashierContent(content, returnFocusEl) {
open(
cashierHeader() + '' + content + '
',
returnFocusEl,
'cashier'
);
}
function ensureCaptchaThen(sourceEl, callback) {
if (!isCaptchaActive(sourceEl)) { callback({}); return; }
const captcha = window.AKSRTCaptcha;
if (!captcha) {
if (window.console && console.warn) console.warn('[AKSRT] download captcha not ready');
alert('验证码组件加载中,请稍候再试');
return;
}
captcha.run('download', captchaScope(sourceEl)).then(function (payload) {
if (payload !== null) callback(payload);
});
}
function performDownloadRequest(button, captchaPayload) {
const card = button.closest('[data-aksrt-download-box]');
if (!card) return;
const postId = parseInt(card.getAttribute('data-post-id'), 10);
const sourceIndex = parseInt(button.getAttribute('data-source-index'), 10);
if (!postId || isNaN(sourceIndex)) return;
button.disabled = true;
const original = button.innerHTML;
button.innerHTML = ' 处理中…';
const restNonce = window.aksrtData.restNonce || '';
fetch(REST_BASE + '/download/request', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': restNonce,
},
body: JSON.stringify({
post_id: postId,
source_index: sourceIndex,
captcha: captchaPayload,
}),
})
.then(function (resp) { return resp.json().then(function (j) { return { ok: resp.ok, json: j }; }); })
.then(function (r) {
button.disabled = false;
button.innerHTML = original;
if (!r.ok || (r.json && r.json.code)) {
renderError(r.json.message || '请稍后重试', r.json.data || {}, button, r.json.code);
return;
}
renderSuccess(r.json, button);
})
.catch(function () {
button.disabled = false;
button.innerHTML = original;
renderError('网络错误,请稍后重试', {}, button);
})
.finally(function () {
if (window.AKSRTCaptcha) window.AKSRTCaptcha.reset('download', captchaScope(button));
});
}
function requestDownload(button) {
ensureCaptchaThen(button, function (captchaPayload) {
performDownloadRequest(button, captchaPayload);
});
}
function pollOrderPaid(orderNumber, returnFocusEl) {
if (!orderNumber) return;
let tries = 0;
const maxTries = 40;
const timer = setInterval(function () {
tries += 1;
fetch(REST_BASE + '/pay/content/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 showLowCreditModal(button) {
const balance = parseInt(button.getAttribute('data-balance'), 10) || 0;
const cost = parseInt(button.getAttribute('data-cost'), 10) || 0;
renderError('', { balance: balance, cost: cost }, button, 'aksrt_points_insufficient');
}
// ===== 收银台(内容购买) =====
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(
'加载中…
',
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 '
';
}
function cashierPriceRow(label, value, className) {
return '' +
'
' + label + '' +
'
' + value + '
' +
'
';
}
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 = '' + baseLabel + '';
if (!isPoints && Number(preview.original) > Number(preview.base)) {
regularPrice = '' + formatAssetLabel(preview, preview.original) + '' + regularPrice;
}
priceRows += cashierPriceRow('价格', regularPrice, 'aksrt-cashier__regular-price');
}
if (preview.list_amount !== preview.base) {
priceRows += cashierPriceRow(
'会员价VIP' + escapeHtml(preview.vip_level || '') + '',
'' + listLabel + '',
'c-red'
);
} else if (preview.amount !== preview.base && !preview.rebate_discount && !preview.coupon_discount) {
priceRows += cashierPriceRow(
'会员价VIP' + escapeHtml(preview.vip_level || '') + '',
'' + amountLabel + '',
'c-red'
);
}
if (preview.rebate_discount > 0) {
priceRows += cashierPriceRow(
'推广折扣',
'' + escapeHtml(preview.rebate_discount_label || ('-' + preview.rebate_discount)) + ''
);
}
if (preview.coupon_discount > 0) {
priceRows += cashierPriceRow(
'优惠码',
'' + escapeHtml(preview.coupon_discount_label || ('-' + preview.coupon_discount)) + ''
);
}
if (preview.amount !== preview.base || preview.rebate_discount > 0 || preview.coupon_discount > 0) {
priceRows += cashierPriceRow(
'支付金额',
'' + amountLabel + '',
'font-bold c-red'
);
}
let body = '';
body += '' +
'' + typeLabel + '' + title + '' +
'
';
body += '' + priceRows + '
';
if (isPoints) {
body += '' +
'
' +
'我的积分' +
'' + balance + '
';
if (!preview.balance_enough) {
body += '抱歉,您的积分不足,暂时无法支付
';
body += '';
if (preview.credits_url) {
body += '
我的积分';
}
body += '
';
openCashierContent(body, returnFocusEl);
return;
}
body += '';
} else {
if (couponAllowed) {
body += '';
}
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 += '' +
paymentMethodLogo(rawId) + '
' + name + '
';
});
if (methods.length > 1) {
body += '请选择支付方式
' +
'' + methodHtml + '
';
}
if (methods.length) {
body += '';
}
if (hasBalanceMethod) {
const balanceSelected = methods[0] && methods[0].id === 'balance';
body += '' +
'
' +
'我的余额' +
'' + escapeHtml(walletBal) + '
';
if (preview.balance_enough === false) {
if (preview.credits_url) {
body += '
' +
'抱歉!余额不足,请先充值 ';
} else {
body += '
抱歉!余额不足,请先充值
';
}
}
body += '
';
}
if (methods.length) {
body += '';
} else {
body += '暂无可用支付方式,请联系站长配置收款接口
';
}
}
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.redirect && (data.payment_url || data.qr_content)) {
if (data.payment_url) {
window.open(data.payment_url, '_blank');
}
renderScanPay(data, returnFocusEl);
if (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 || {};
let body = '';
if (data.qr_content) {
body += '凭证内容
' +
'
' + escapeHtml(String(data.qr_content)) + ' ';
}
body += '' + escapeHtml(data.message || '请使用手机扫码完成支付,支付后刷新页面。') + '
';
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 lowBtn = e.target.closest('[data-action="aksrt-need-credits"]');
if (lowBtn) {
e.preventDefault();
showLowCreditModal(lowBtn);
return;
}
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;
}
const btn = e.target.closest('[data-action="aksrt-download"]');
if (!btn) return;
if (btn.disabled || btn.getAttribute('aria-disabled') === 'true') return;
e.preventDefault();
requestDownload(btn);
});
// Public cashier bridge (content pay).
window.aksrtOpenCashier = function (postId, returnFocusEl, priceType, couponCode) {
openCashier(postId, returnFocusEl || null, priceType || '', couponCode || '');
};
})();