feat(pay): content cashier, resource orders at VIP price, purchase/download JS loop
Wire zibll-style one-shot points/cash cashier for post+_aksrt_pay downloads: server amount from aksrt_get_user_pay_price, ORDER_PRODUCT_RESOURCE unlock, frontend need-purchase opens modal, download box purchased/VIP/purchase CTAs.
This commit is contained in:
1 parent
abe2f57417
commit
770205623d
11 files changed
+1142
-170
No files matched your search
+249
-3
@@ -140,13 +140,29 @@
|
||||
title = tt('need_login', '请先登录');
|
||||
msg = tt('login_hint', '登录后即可继续下载,访客不可使用此资源。');
|
||||
extra = '<a class="btn-primary btn-sm" href="' + data.login_url + '">' + tt('go_login', '前往登录') + '</a>';
|
||||
} else if (code === 'aksrt_credits_insufficient') {
|
||||
} else if (code === 'aksrt_need_purchase') {
|
||||
title = tt('need_purchase', '请先购买');
|
||||
msg = msg || tt('purchase_hint', '请先完成购买后再下载该资源。');
|
||||
extra = '<button type="button" class="btn-primary btn-sm" data-aksrt-open-cashier="' + escapeAttr((data && data.post_id) || '') + '">' + tt('go_buy', '去购买') + '</button>';
|
||||
} else if (code === 'aksrt_need_vip') {
|
||||
title = tt('need_vip', '会员专属');
|
||||
msg = msg || tt('vip_hint', '该资源仅限会员购买/下载。');
|
||||
if (data && data.vip_url) {
|
||||
extra = '<a class="btn-primary btn-sm" href="' + escapeAttr(data.vip_url) + '">' + tt('go_vip', '开通会员') + '</a>';
|
||||
}
|
||||
} else if (code === 'aksrt_credits_insufficient' || code === 'aksrt_balance_insufficient') {
|
||||
title = tt('credit_low', '积分不够');
|
||||
const balance = typeof (data && data.balance) === 'number' ? data.balance : 0;
|
||||
const cost = typeof (data && data.cost) === 'number' ? data.cost : 0;
|
||||
msg = tt('credit_hint',
|
||||
'当前余额 ' + balance + ' 积分,需要 ' + cost + ' 积分。可通过签到、阅读、邀请好友等方式获取积分。'
|
||||
);
|
||||
if (data && data.credits_url) {
|
||||
extra = '<a class="btn-primary btn-sm" href="' + escapeAttr(data.credits_url) + '">' + tt('my_points', '我的积分') + '</a>';
|
||||
}
|
||||
} else if (code === 'aksrt_captcha_failed') {
|
||||
title = tt('captcha_fail', '验证失败');
|
||||
msg = msg || tt('captcha_retry', '人机验证失败,请重试。');
|
||||
} else if (data && data.login_url) {
|
||||
extra = '<a class="btn-primary btn-sm" href="' + data.login_url + '">' + tt('go_login', '前往登录') + '</a>';
|
||||
}
|
||||
@@ -157,6 +173,15 @@
|
||||
(extra ? '<div class="aksrt-dl-modal__actions">' + extra + '</div>' : ''),
|
||||
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;
|
||||
close();
|
||||
if (pid) openCashier(pid, returnFocusEl);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 成功渲染 =====
|
||||
@@ -303,6 +328,189 @@
|
||||
renderError('', { balance: balance, cost: cost }, button, 'aksrt_credits_insufficient');
|
||||
}
|
||||
|
||||
// ===== 收银台(内容购买) =====
|
||||
function restHeaders() {
|
||||
return {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WP-Nonce': window.aksrtData.restNonce || '',
|
||||
};
|
||||
}
|
||||
|
||||
function openCashier(postId, returnFocusEl) {
|
||||
if (!postId) return;
|
||||
open(
|
||||
'<h3 class="aksrt-dl-modal__title">' + tt('cashier_loading', '确认购买') + '</h3>' +
|
||||
'<p class="aksrt-dl-modal__msg">' + tt('loading', '加载中…') + '</p>',
|
||||
returnFocusEl
|
||||
);
|
||||
|
||||
fetch(REST_BASE + '/pay/content/' + postId, {
|
||||
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 }; }); })
|
||||
.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) || tt('default_err', '请稍后重试'), data, returnFocusEl, code);
|
||||
return;
|
||||
}
|
||||
renderCashier(r.json, returnFocusEl);
|
||||
})
|
||||
.catch(function () {
|
||||
renderError(tt('network_err', '网络错误,请稍后重试'), {}, returnFocusEl);
|
||||
});
|
||||
}
|
||||
|
||||
function renderCashier(preview, returnFocusEl) {
|
||||
if (!preview) return;
|
||||
if (preview.need_login) {
|
||||
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 || preview.is_free) {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
const isPoints = preview.pay_modo === 'points';
|
||||
const amountLabel = escapeHtml(preview.amount_label || '');
|
||||
const baseLabel = escapeHtml(preview.base_label || '');
|
||||
const title = escapeHtml(preview.title || '');
|
||||
const typeLabel = escapeHtml(preview.pay_type_label || '');
|
||||
const balance = typeof preview.balance === 'number' ? preview.balance : 0;
|
||||
const methods = Array.isArray(preview.payment_methods) ? preview.payment_methods : [];
|
||||
|
||||
let priceRows = '';
|
||||
if (!preview.pay_limit) {
|
||||
priceRows += '<div class="aksrt-cashier__row"><span>' + tt('price', '价格') + '</span><b>' + baseLabel + '</b></div>';
|
||||
}
|
||||
if (preview.amount !== preview.base) {
|
||||
priceRows += '<div class="aksrt-cashier__row"><span>' + tt('vip_price', '会员价') + '</span><b class="is-sale">' + amountLabel + '</b></div>';
|
||||
}
|
||||
priceRows += '<div class="aksrt-cashier__row"><span>' + tt('pay_amount', '支付金额') + '</span><b class="is-sale">' + amountLabel + '</b></div>';
|
||||
|
||||
let body = '';
|
||||
body += '<div class="aksrt-cashier__product"><span class="aksrt-cashier__tag">' + typeLabel + '</span><span>' + title + '</span></div>';
|
||||
body += '<div class="aksrt-cashier__box">' + priceRows + '</div>';
|
||||
|
||||
if (isPoints) {
|
||||
body += '<div class="aksrt-cashier__box"><div class="aksrt-cashier__row"><span>' + tt('my_points', '我的积分') + '</span><b class="is-ok">' + balance + '</b></div></div>';
|
||||
if (!preview.balance_enough) {
|
||||
body += '<div class="aksrt-cashier__warn">' + tt('points_short', '抱歉,您的积分不足,暂时无法支付') + '</div>';
|
||||
body += '<div class="aksrt-dl-modal__actions">';
|
||||
if (preview.credits_url) {
|
||||
body += '<a class="btn-primary btn-sm" href="' + escapeAttr(preview.credits_url) + '">' + tt('my_points', '我的积分') + '</a>';
|
||||
}
|
||||
body += '</div>';
|
||||
open('<h3 class="aksrt-dl-modal__title">' + tt('confirm_buy', '确认购买') + '</h3>' + body, returnFocusEl);
|
||||
return;
|
||||
}
|
||||
body += '<div class="aksrt-dl-modal__actions">';
|
||||
body += '<button type="button" class="btn-primary aksrt-dl-modal__download" data-aksrt-pay-submit data-mode="points">' + tt('pay_now', '立即支付') + ' ' + amountLabel + '</button>';
|
||||
body += '</div>';
|
||||
} else {
|
||||
let methodHtml = '';
|
||||
methods.forEach(function (m, idx) {
|
||||
const id = escapeAttr(m.id || '');
|
||||
const name = escapeHtml(m.name || id);
|
||||
methodHtml += '<label class="aksrt-cashier__method"><input type="radio" name="aksrt_pay_method" value="' + id + '"' + (idx === 0 ? ' checked' : '') + '> <span>' + name + '</span></label>';
|
||||
});
|
||||
if (methodHtml) {
|
||||
body += '<div class="aksrt-cashier__methods"><div class="aksrt-cashier__methods-title">' + tt('pay_method', '支付方式') + '</div>' + methodHtml + '</div>';
|
||||
}
|
||||
body += '<div class="aksrt-dl-modal__actions">';
|
||||
body += '<button type="button" class="btn-primary aksrt-dl-modal__download" data-aksrt-pay-submit data-mode="cash">' + tt('pay_now', '立即支付') + ' ' + amountLabel + '</button>';
|
||||
body += '</div>';
|
||||
}
|
||||
|
||||
open('<h3 class="aksrt-dl-modal__title">' + tt('confirm_buy', '确认购买') + '</h3>' + body, returnFocusEl);
|
||||
|
||||
const submitBtn = document.querySelector('[data-aksrt-pay-submit]');
|
||||
if (submitBtn) {
|
||||
submitBtn.addEventListener('click', function () {
|
||||
const mode = submitBtn.getAttribute('data-mode') || (isPoints ? 'points' : 'cash');
|
||||
let paymentMethod = 'balance';
|
||||
const checked = document.querySelector('input[name="aksrt_pay_method"]:checked');
|
||||
if (checked) paymentMethod = checked.value;
|
||||
submitPurchase(preview.post_id, mode, paymentMethod, submitBtn, returnFocusEl);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function submitPurchase(postId, mode, paymentMethod, button, returnFocusEl) {
|
||||
if (!postId) return;
|
||||
const original = button.innerHTML;
|
||||
button.disabled = true;
|
||||
button.innerHTML = tt('processing', '处理中…');
|
||||
|
||||
const payload = { mode: mode };
|
||||
if (mode === 'cash') payload.payment_method = paymentMethod || 'balance';
|
||||
|
||||
fetch(REST_BASE + '/pay/content/' + postId, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: restHeaders(),
|
||||
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) || tt('default_err', '请稍后重试'), data, returnFocusEl, code);
|
||||
return;
|
||||
}
|
||||
const data = (r.json && r.json.data) ? r.json.data : r.json;
|
||||
if (data && data.redirect && (data.payment_url || data.qr_content)) {
|
||||
if (data.payment_url) {
|
||||
window.location.href = data.payment_url;
|
||||
return;
|
||||
}
|
||||
open(
|
||||
'<h3 class="aksrt-dl-modal__title">' + tt('scan_pay', '扫码支付') + '</h3>' +
|
||||
'<p class="aksrt-dl-modal__msg">' + escapeHtml(data.message || tt('scan_hint', '请使用手机扫码完成支付,支付后刷新页面。')) + '</p>' +
|
||||
'<p class="aksrt-dl-modal__msg"><code>' + escapeHtml(data.qr_content || '') + '</code></p>' +
|
||||
'<div class="aksrt-dl-modal__actions"><button type="button" class="btn-primary btn-sm" data-aksrt-reload>' + tt('paid_refresh', '我已支付') + '</button></div>',
|
||||
returnFocusEl
|
||||
);
|
||||
const reloadBtn = document.querySelector('[data-aksrt-reload]');
|
||||
if (reloadBtn) reloadBtn.addEventListener('click', function () { window.location.reload(); });
|
||||
return;
|
||||
}
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(function () {
|
||||
button.disabled = false;
|
||||
button.innerHTML = original;
|
||||
renderError(tt('network_err', '网络错误,请稍后重试'), {}, returnFocusEl);
|
||||
});
|
||||
}
|
||||
|
||||
// ===== 事件绑定 =====
|
||||
document.addEventListener('click', function (e) {
|
||||
const lowBtn = e.target.closest('[data-action="aksrt-need-credits"]');
|
||||
@@ -312,6 +520,28 @@
|
||||
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
|
||||
);
|
||||
if (postId) openCashier(postId, buyBtn);
|
||||
return;
|
||||
}
|
||||
|
||||
const cashierLink = e.target.closest('.cashier-link');
|
||||
if (cashierLink && cashierLink.hasAttribute('data-aksrt-pay-content')) {
|
||||
e.preventDefault();
|
||||
const postId = parseInt(cashierLink.getAttribute('data-aksrt-pay-content') || '0', 10);
|
||||
if (postId) openCashier(postId, cashierLink);
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = e.target.closest('[data-action="aksrt-download"]');
|
||||
if (!btn) return;
|
||||
if (btn.disabled || btn.getAttribute('aria-disabled') === 'true') return;
|
||||
@@ -338,9 +568,25 @@
|
||||
'.aksrt-dl-modal__copy:hover{background:#dbeafe!important;border-color:#93c5fd!important;color:#1d4ed8!important;-webkit-text-fill-color:#1d4ed8!important;}' +
|
||||
'.aksrt-dl-modal__copy-text{display:inline-flex!important;align-items:center!important;justify-content:center!important;position:relative!important;z-index:2!important;color:#2563eb!important;-webkit-text-fill-color:#2563eb!important;font-size:13px!important;font-weight:700!important;line-height:1!important;font-family:inherit!important;text-indent:0!important;opacity:1!important;visibility:visible!important;white-space:nowrap!important;}' +
|
||||
'.aksrt-dl-modal__copy:hover .aksrt-dl-modal__copy-text{color:#1d4ed8!important;-webkit-text-fill-color:#1d4ed8!important;}' +
|
||||
'.aksrt-dl-modal__actions{display:flex;align-items:center;gap:12px;padding-top:4px;}' +
|
||||
'.aksrt-dl-modal__download{min-height:40px;padding:0 18px;border-radius:9px;display:inline-flex;align-items:center;justify-content:center;text-decoration:none;}' +
|
||||
'.aksrt-dl-modal__actions{display:flex;align-items:center;gap:12px;padding-top:4px;flex-wrap:wrap;}' +
|
||||
'.aksrt-dl-modal__download{min-height:40px;padding:0 18px;border-radius:9px;display:inline-flex;align-items:center;justify-content:center;text-decoration:none;border:0;cursor:pointer;}' +
|
||||
'.aksrt-dl-modal__balance{font-size:13px;color:#6b7280;margin-left:auto;white-space:nowrap;}' +
|
||||
'.aksrt-cashier__product{margin:0 0 12px;font-size:14px;line-height:1.5;}' +
|
||||
'.aksrt-cashier__tag{display:inline-block;margin-right:8px;padding:2px 8px;border-radius:6px;background:#eff6ff;color:#1d4ed8;font-size:12px;font-weight:700;}' +
|
||||
'.aksrt-cashier__box{background:#f8fafc;border:1px solid #e5e7eb;border-radius:10px;padding:10px 12px;margin:0 0 12px;}' +
|
||||
'.aksrt-cashier__row{display:flex;justify-content:space-between;align-items:center;gap:12px;font-size:13px;color:#6b7280;padding:4px 0;}' +
|
||||
'.aksrt-cashier__row b{color:#111827;font-size:15px;}' +
|
||||
'.aksrt-cashier__row b.is-sale{color:#dc2626;}' +
|
||||
'.aksrt-cashier__row b.is-ok{color:#059669;}' +
|
||||
'.aksrt-cashier__warn{background:#fef2f2;color:#b91c1c;border-radius:8px;padding:10px 12px;margin:0 0 12px;font-size:13px;}' +
|
||||
'.aksrt-cashier__methods{margin:0 0 14px;}' +
|
||||
'.aksrt-cashier__methods-title{font-size:13px;font-weight:600;color:#374151;margin:0 0 8px;}' +
|
||||
'.aksrt-cashier__method{display:flex;align-items:center;gap:8px;padding:8px 10px;border:1px solid #e5e7eb;border-radius:8px;margin:0 0 6px;cursor:pointer;font-size:13px;}' +
|
||||
'.resource-download-box__attrs{list-style:none;margin:10px 0 0;padding:0;display:grid;gap:6px;}' +
|
||||
'.resource-download-box__attrs-item{display:flex;justify-content:space-between;gap:12px;font-size:12px;color:#6b7280;}' +
|
||||
'.resource-download-box__attrs-name{flex:0 0 auto;}' +
|
||||
'.resource-download-box__attrs-value{color:#111827;text-align:right;}' +
|
||||
'.resource-download-box__paid-badge{background:#ecfdf5!important;color:#047857!important;}' +
|
||||
'@media(max-width:480px){.aksrt-dl-modal__panel{padding:24px 20px 22px;}.aksrt-dl-modal__row{grid-template-columns:1fr 72px;gap:8px 12px;}.aksrt-dl-modal__label{grid-column:1 / -1;}.aksrt-dl-modal__code{font-size:17px;}.aksrt-dl-modal__actions{align-items:flex-start;flex-direction:column;}.aksrt-dl-modal__download{width:100%;}.aksrt-dl-modal__balance{margin-left:0;}}' +
|
||||
'body.aksrt-dl-modal-open{overflow:hidden;}';
|
||||
document.head.appendChild(style);
|
||||
|
||||
Reference in new issue
Block a user