feat(pay): content cashier coupon and rebate discount chain
Wire zibll-style cash content discounts: VIP list price → buyer 推广折扣 → coupon, then commission on final pay. Reuse AKSRT_Coupon, add Invite::get_referrer, cashier UI, and paid-time coupon burn + rebate_log.
This commit is contained in:
1 parent
9eaddce457
commit
5d7b138c2c
11 files changed
+724
-22
No files matched your search
+97
-13
@@ -366,23 +366,36 @@
|
||||
};
|
||||
}
|
||||
|
||||
function openCashier(postId, returnFocusEl, priceType) {
|
||||
function buildPreviewUrl(postId, priceType, couponCode) {
|
||||
var previewUrl = REST_BASE + '/pay/content/' + 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 || '';
|
||||
open(
|
||||
'<h3 class="aksrt-dl-modal__title">' + tt('cashier_loading', '确认购买') + '</h3>' +
|
||||
'<p class="aksrt-dl-modal__msg">' + tt('loading', '加载中…') + '</p>',
|
||||
returnFocusEl
|
||||
);
|
||||
|
||||
var previewUrl = REST_BASE + '/pay/content/' + postId;
|
||||
if (priceType) previewUrl += (previewUrl.indexOf('?') >= 0 ? '&' : '?') + 'price_type=' + encodeURIComponent(priceType);
|
||||
fetch(previewUrl, {
|
||||
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 }; }); })
|
||||
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 : '';
|
||||
@@ -404,6 +417,7 @@
|
||||
}
|
||||
var prev = r.json || {};
|
||||
if (priceType) prev._price_type = priceType;
|
||||
if (couponCode) prev._coupon_code = couponCode;
|
||||
renderCashier(prev, returnFocusEl);
|
||||
})
|
||||
.catch(function () {
|
||||
@@ -430,7 +444,8 @@
|
||||
const priceType = (preview._price_type || preview.price_type || '');
|
||||
let amountLabel = escapeHtml(preview.amount_label || '');
|
||||
let baseLabel = escapeHtml(preview.base_label || '');
|
||||
if (priceType === 'download_limit_over' && preview.download_limit_over_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;
|
||||
@@ -440,14 +455,25 @@
|
||||
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 : [];
|
||||
const couponAllowed = !isPoints && !!preview.coupon_allowed;
|
||||
const couponCodeVal = escapeAttr(preview.coupon_code || preview._coupon_code || '');
|
||||
|
||||
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) {
|
||||
// VIP list price row when VIP reduced list amount.
|
||||
if (typeof preview.list_amount === 'number' && typeof preview.base === 'number' && preview.list_amount !== preview.base) {
|
||||
priceRows += '<div class="aksrt-cashier__row"><span>' + tt('vip_price', '会员价') + '</span><b class="is-sale">' + listLabel + '</b></div>';
|
||||
} else if (preview.amount !== preview.base && !preview.rebate_discount && !preview.coupon_discount) {
|
||||
priceRows += '<div class="aksrt-cashier__row"><span>' + tt('vip_price', '会员价') + '</span><b class="is-sale">' + amountLabel + '</b></div>';
|
||||
}
|
||||
if (preview.rebate_discount > 0) {
|
||||
priceRows += '<div class="aksrt-cashier__row"><span>' + tt('rebate_discount', '推广折扣') + '</span><b class="is-sale">' + escapeHtml(preview.rebate_discount_label || ('-' + preview.rebate_discount)) + '</b></div>';
|
||||
}
|
||||
if (preview.coupon_discount > 0) {
|
||||
priceRows += '<div class="aksrt-cashier__row"><span>' + tt('coupon_discount', '优惠码') + '</span><b class="is-sale">' + escapeHtml(preview.coupon_discount_label || ('-' + preview.coupon_discount)) + '</b></div>';
|
||||
}
|
||||
priceRows += '<div class="aksrt-cashier__row"><span>' + tt('pay_amount', '支付金额') + '</span><b class="is-sale">' + amountLabel + '</b></div>';
|
||||
|
||||
let body = '';
|
||||
@@ -470,6 +496,23 @@
|
||||
body += '<button type="button" class="btn-primary aksrt-dl-modal__download" data-aksrt-pay-submit data-mode="points" data-price-type="' + escapeAttr(priceType) + '">' + tt('pay_now', '立即支付') + ' ' + amountLabel + '</button>';
|
||||
body += '</div>';
|
||||
} else {
|
||||
if (couponAllowed) {
|
||||
body += '<div class="aksrt-cashier__coupon">';
|
||||
body += '<div class="aksrt-cashier__methods-title">' + tt('use_coupon', '使用优惠码') + '</div>';
|
||||
body += '<div class="aksrt-cashier__coupon-row">';
|
||||
body += '<input type="text" class="aksrt-cashier__coupon-input" name="aksrt_coupon_code" value="' + couponCodeVal + '" placeholder="' + escapeAttr(tt('enter_coupon', '请输入优惠码')) + '" autocomplete="off">';
|
||||
body += '<button type="button" class="btn-sm" data-aksrt-check-coupon>' + tt('check_coupon', '检查优惠码') + '</button>';
|
||||
body += '</div>';
|
||||
if (preview.coupon_desc) {
|
||||
body += '<div class="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">' + tt('coupon_ok', '优惠码可用') + '</div>';
|
||||
}
|
||||
body += '</div>';
|
||||
}
|
||||
let methodHtml = '';
|
||||
methods.forEach(function (m, idx) {
|
||||
const id = escapeAttr(m.id || '');
|
||||
@@ -486,6 +529,39 @@
|
||||
|
||||
open('<h3 class="aksrt-dl-modal__title">' + tt('confirm_buy', '确认购买') + '</h3>' + body, returnFocusEl);
|
||||
|
||||
const checkBtn = document.querySelector('[data-aksrt-check-coupon]');
|
||||
if (checkBtn) {
|
||||
checkBtn.addEventListener('click', function () {
|
||||
const input = document.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 = tt('checking', '检查中…');
|
||||
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) || tt('default_err', '请稍后重试'), (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(tt('network_err', '网络错误,请稍后重试'), {}, returnFocusEl);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const submitBtn = document.querySelector('[data-aksrt-pay-submit]');
|
||||
if (submitBtn) {
|
||||
submitBtn.addEventListener('click', function () {
|
||||
@@ -493,12 +569,14 @@
|
||||
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);
|
||||
const input = document.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) {
|
||||
function submitPurchase(postId, mode, paymentMethod, button, returnFocusEl, couponCode) {
|
||||
if (!postId) return;
|
||||
const original = button.innerHTML;
|
||||
button.disabled = true;
|
||||
@@ -508,6 +586,7 @@
|
||||
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;
|
||||
|
||||
fetch(REST_BASE + '/pay/content/' + postId, {
|
||||
method: 'POST',
|
||||
@@ -627,6 +706,11 @@
|
||||
'.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;}' +
|
||||
'.aksrt-cashier__coupon{margin:0 0 14px;}' +
|
||||
'.aksrt-cashier__coupon-row{display:flex;gap:8px;align-items:center;}' +
|
||||
'.aksrt-cashier__coupon-input{flex:1;min-width:0;padding:8px 10px;border:1px solid #e5e7eb;border-radius:8px;font-size:13px;}' +
|
||||
'.aksrt-cashier__coupon-desc{margin-top:6px;font-size:12px;color:#9ca3af;line-height:1.5;}' +
|
||||
'.aksrt-cashier__coupon-ok{margin-top:6px;font-size:12px;color:#059669;}' +
|
||||
'.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;}' +
|
||||
|
||||
Reference in new issue
Block a user