feat(revenue): immediate commission credit with independent withdrawable pool

Aligns affiliate commission with the reference theme: commission is credited
to a dedicated COMMISSION asset pool the moment an order is paid, removing the
7-day settlement freeze, the settlement cron, and the settlement_days setting.

- Ledger: add AssetType::COMMISSION (isolated from the recharge balance wallet)
- CommissionLedger: credit-on-record + opportunistic debt repayment; refunds
  claw back the full commission from the pool, carrying any shortfall as debt
  (aksrt_commission_debt) that future commission pays down first
- Withdrawal: make asset-aware via a source discriminator (commission vs
  balance); each pool keeps independent daily limits, pending gate, and
  reject-refund. Balance withdrawal (recharge + creator income) is unchanged
- Fix pay_rebate_self_s: honor self-commission at purchase time
- Revenue card: show pool balance + 累计/已提现 (drop 待结算), debt when present
- Schema 2.0.0 -> 2.1.0: add commission_debt, drop freeze columns
This commit is contained in:
kate522 committed 2026-09-28 21:27:44 +08:00
1 parent cded20ae02
commit 61667aa03c
22 files changed
+569 -268

No files matched your search

-1
View File
@@ -3,7 +3,6 @@
@import url("user-center/vip.css");
@import url("user-center/level.css");
@import url("user-center/settings.css");
@import url("revenue.css");
@import url("user-center/revenue.css");
@import url("user-center/revenue-earnings.css");
@import url("user-center/responsive.css");
@@ -27,12 +27,16 @@
}
.uc-content .uc-commission-summary {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px;
}
.uc-content .uc-commission-card {
position: relative;
min-height: 116px;
padding: 16px 22px;
overflow: hidden;
border: 0;
border-radius: 18px;
box-shadow: 0 12px 28px rgba(15, 23, 42, .08);
@@ -56,7 +60,10 @@
.uc-content .uc-commission-card__body {
display: grid;
position: relative;
z-index: 1;
align-content: space-between;
gap: 8px;
min-height: 84px;
}
@@ -69,29 +76,73 @@
.uc-content .uc-commission-card__body > span:first-child {
margin: 0;
font-size: 18px;
font-weight: 700;
font-size: 15px;
opacity: .85;
}
.uc-content .uc-commission-card__body strong {
font-size: 32px;
font-size: 34px;
font-weight: 700;
line-height: 1;
}
.uc-content .uc-commission-card__body strong small {
margin-left: 4px;
font-size: 15px;
font-weight: 400;
}
.uc-content .uc-commission-card--ratio .uc-commission-card__body strong small {
margin-left: 6px;
font-size: 20px;
}
.uc-content .uc-commission-card__body > span:last-child,
.uc-content .uc-commission-card__body > p {
margin: 0;
font-size: 14px;
opacity: .92;
font-size: 13px;
opacity: .9;
}
.uc-content .uc-commission-card--ratio .uc-commission-card__body strong {
color: inherit;
/* 我的佣金:累计 / 已提现(含欠款)副信息。 */
.uc-content .uc-commission-card__meta {
display: flex;
flex-wrap: wrap;
gap: 4px 16px;
}
/* 待偿欠款:退款收回不足挂账,副信息里以描边标签弱提示。 */
.uc-content .uc-commission-card__debt {
padding: 0 8px;
border-radius: 999px;
background: rgba(255, 255, 255, .22);
}
/* 立即提现按钮:贴右居中,对齐参考主题 .abs-right。 */
.uc-content .uc-commission-card__action {
position: absolute;
top: 50%;
right: 16px;
z-index: 2;
display: inline-flex;
align-items: center;
gap: 4px;
padding: 7px 14px;
transform: translateY(-50%);
border: 0;
border-radius: 999px;
color: #fff;
background: rgba(255, 255, 255, .22);
font-size: 13px;
cursor: pointer;
}
.uc-content .uc-commission-card__action:hover {
background: rgba(255, 255, 255, .32);
}
/* 提现按钮所在卡片:正文右侧预留空间,避免与按钮重叠。 */
.uc-content .uc-commission-card--balance:has(.uc-commission-card__action) .uc-commission-card__body {
padding-right: 96px;
}
/* 三 Tab 推广收益:佣金详细页采用紧凑的一列信息清单。 */
@@ -85,6 +85,12 @@
gap: 16px;
}
/* 三个 Tab 面板首屏全部直出,靠 hidden 属性做本地切换;
* display:grid 会覆盖 [hidden] 的 display:none,必须显式复位。 */
.uc-reward-pane[hidden] {
display: none;
}
.uc-reward-pane--detail {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
+23 -5
View File
@@ -934,8 +934,24 @@
modal.dataset.aksrtWithdrawInitialized = '1';
var form = modal.querySelector('[data-aksrt-withdraw-form]');
var busy = false;
// 提现来源随打开按钮传入:commission 走佣金池,其余走余额钱包。
// 每个来源的每日次数/额度不同,按钮上带了各自的 min/remaining,
// 打开时覆盖到金额输入框,提交时把 source 一并回传。
document.querySelectorAll('[data-aksrt-withdraw-open]').forEach(function (btn) {
btn.addEventListener('click', function () { openModal(modal); });
btn.addEventListener('click', function () {
var src = btn.getAttribute('data-aksrt-withdraw-source') || 'balance';
modal.dataset.aksrtWithdrawSource = src === 'commission' ? 'commission' : 'balance';
var amountEl = form ? form.querySelector('[data-aksrt-withdraw-amount]') : null;
if (amountEl) {
var min = btn.getAttribute('data-withdraw-min');
var remAmt = btn.getAttribute('data-withdraw-remaining-amount');
var remCnt = btn.getAttribute('data-withdraw-remaining-count');
if (min !== null) amountEl.setAttribute('data-withdraw-min', min);
if (remAmt !== null) amountEl.setAttribute('data-withdraw-remaining-amount', remAmt);
if (remCnt !== null) amountEl.setAttribute('data-withdraw-remaining-count', remCnt);
}
openModal(modal);
});
});
modal.querySelectorAll('[data-aksrt-withdraw-close]').forEach(function (btn) {
btn.addEventListener('click', function () { closeModal(modal); });
@@ -978,10 +994,11 @@
}
busy = true;
notyf('正在提交提现申请…', 'load', '', 'aksrt-balance-withdraw-toast');
var source = modal.dataset.aksrtWithdrawSource === 'commission' ? 'commission' : 'balance';
api('/earnings/withdrawals', {
method: 'POST',
headers: { 'Idempotency-Key': operationKey(form) },
body: JSON.stringify({ amount: amount, method: method, account: account })
body: JSON.stringify({ amount: amount, method: method, account: account, source: source })
}).then(function (r) {
busy = false;
if (!r.ok || (r.json && r.json.code && !r.json.success)) {
@@ -994,11 +1011,12 @@
if (data && data.withdrawal_number) successMessage += '(' + data.withdrawal_number + ')';
notyf(successMessage, 'success', 5, 'aksrt-balance-withdraw-toast');
closeModal(modal);
var panel = modal.closest ? modal.closest('.dashboard-panel') : null;
panel = panel || document.getElementById('panel-credits');
// 佣金提现刷新推广收益面板,余额提现刷新余额面板;各自拉取对应片段。
var panelName = source === 'commission' ? 'revenue' : 'credits';
var panel = document.getElementById('panel-' + panelName) || (modal.closest ? modal.closest('.dashboard-panel') : null);
var rest = (window.aksrtData && (window.aksrtData.restUrl || window.aksrtData.apiUrl)) || (window.aksrtUserCenter && window.aksrtUserCenter.restUrl) || '';
if (panel && rest) {
fetch(rest.replace(/\/$/, '') + '/user-center/panel/credits', { credentials: 'same-origin', headers: { 'X-WP-Nonce': (window.aksrtData && (window.aksrtData.restNonce || window.aksrtData.nonce)) || '' } })
fetch(rest.replace(/\/$/, '') + '/user-center/panel/' + panelName, { credentials: 'same-origin', headers: { 'X-WP-Nonce': (window.aksrtData && (window.aksrtData.restNonce || window.aksrtData.nonce)) || '' } })
.then(function (response) { return response.json(); })
.then(function (payload) {
if (!payload || !payload.success || !payload.data || typeof payload.data.html !== 'string') return;
+27
View File
@@ -158,6 +158,26 @@
return true;
}
/* 推广收益三个 Tab 的内容在首屏已由 PHP 全部直出,切换只需本地切换
* 显隐,无需任何网络往返,消除切换阻塞。分页仍走 loadRevenueTab。 */
function toggleRevenueTab(tabName) {
var target = document.getElementById('panel-revenue');
if (!target) return false;
var pane = target.querySelector('[data-reward-pane="' + tabName + '"]');
if (!pane) return false;
target.querySelectorAll('[data-reward-pane]').forEach(function(node) {
var active = node === pane;
node.hidden = !active;
});
target.querySelectorAll('.uc-reward-tabs a[data-reward-tab]').forEach(function(link) {
var active = link.getAttribute('data-reward-tab') === tabName;
link.classList.toggle('is-active', active);
if (active) link.setAttribute('aria-current', 'page');
else link.removeAttribute('aria-current');
});
return true;
}
function loadRevenueTab(queryParams) {
var target = document.getElementById('panel-revenue');
var restUrl = (window.aksrtData && (window.aksrtData.restUrl || window.aksrtData.apiUrl)) || cfg.restUrl || '';
@@ -260,6 +280,13 @@
if (link.closest('.uc-reward-tabs, .uc-reward-pagination')) {
e.preventDefault();
e.stopPropagation();
var rewardTab = link.getAttribute('data-reward-tab');
/* Tab 切换:内容已全部直出,纯本地切换显隐,零网络往返。 */
if (rewardTab && toggleRevenueTab(rewardTab)) {
if (window.history.replaceState) window.history.replaceState({}, '', '/user/revenue/');
return;
}
/* 分页:需要新一页数据,仍走 REST 片段加载。 */
loadRevenueTab(url.searchParams).then(function() {
if (window.history.replaceState) window.history.replaceState({}, '', '/user/revenue/');
if (typeof window.AKSRTAffiliateRevenueInit === 'function') window.AKSRTAffiliateRevenueInit();