diff --git a/assets/js/credits.js b/assets/js/credits.js
index 87320e98..9572507f 100644
--- a/assets/js/credits.js
+++ b/assets/js/credits.js
@@ -115,6 +115,8 @@
}
// ===== Forward-only affiliate revenue center =====
+ /* 推广链接由 PHP 直出,首屏即可见、可复制;这里只补异步才拿得到的
+ * 海报配置与佣金比例,取失败也不影响复制。 */
function initAffiliateRevenue() {
var root = document.querySelector('[data-aksrt-revenue-page]');
if (!root || root.dataset.affiliateBound) return;
@@ -123,16 +125,14 @@
var copy = root.querySelector('[data-affiliate-copy]');
var feedback = root.querySelector('[data-affiliate-feedback]');
function setFeedback(text, error) { if (feedback) { feedback.textContent = text; feedback.classList.toggle('is-error', !!error); } }
+ var url = input ? input.value : '';
+ if (copy) copy.disabled = !url;
api('/revenue/me').then(function (r) {
- if (!r.ok) { setFeedback((r.json && r.json.message) || '推广数据加载失败。', true); return; }
+ if (!r.ok) return;
var j = r.json || {};
- if (input) {
- input.value = j.url || '';
- }
- if (copy) copy.disabled = !j.url;
var ratio = root.querySelector('[data-affiliate-ratio]'); if (ratio) ratio.textContent = String(j.current_ratio || 0) + '%';
- initAffiliatePoster(root, j.url || '', j.poster || {}, j.site_name || document.title);
- }).catch(function () { setFeedback('推广数据加载失败。', true); });
+ initAffiliatePoster(root, url || j.url || '', j.poster || {}, j.site_name || document.title);
+ }).catch(function () { /* 海报与比例为增强项,静默失败即可。 */ });
if (copy) copy.addEventListener('click', function () {
if (!input || !input.value) return;
var done = function () { setFeedback('推广链接已复制。'); };
diff --git a/inc/helpers/user-center-renderers/profile-revenue.php b/inc/helpers/user-center-renderers/profile-revenue.php
index 175f9a62..a990627b 100644
--- a/inc/helpers/user-center-renderers/profile-revenue.php
+++ b/inc/helpers/user-center-renderers/profile-revenue.php
@@ -56,6 +56,11 @@ $details_title = trim((string) ($affiliate['details_title'] ?? ''));
if ($details_title === '') {
$details_title = __('返佣详解', 'aksrt');
}
+/* 推广链接服务端直出:避免为了一个本地拼接的 URL 多绕一次 REST 往返。 */
+$affiliate_link_url = '';
+if (class_exists('\AKSRT\Payment\ReferralAttribution')) {
+ $affiliate_link_url = (string) (\AKSRT\Payment\ReferralAttribution::get_instance()->create_link($user_id)['url'] ?? '');
+}
$revenue_request = is_array($context['revenue_request'] ?? null) ? $context['revenue_request'] : [];
$active_reward_tab = sanitize_key((string) ($revenue_request['reward_tab'] ?? ($_GET['reward_tab'] ?? 'detail')));
@@ -144,7 +149,7 @@ $status_labels = [
-
+
diff --git a/src/Payment/CommissionLedger.php b/src/Payment/CommissionLedger.php
index e8095699..05e6356e 100644
--- a/src/Payment/CommissionLedger.php
+++ b/src/Payment/CommissionLedger.php
@@ -103,14 +103,6 @@ final class CommissionLedger {
foreach ((array) $rows as $row) if ($this->settle_one((int)$row['id'])) $count++;
return $count;
}
- /** 懒结算兜底:收益相关入口顺带结算到期佣金,弥补 wp-cron 在低流量站点的时效缺口。 */
- private static bool $lazy_settled = false;
- public function lazy_settle_due(): void {
- if (self::$lazy_settled) return;
- self::$lazy_settled = true;
- if (!\AKSRT\Settings\Settings::affiliate_enabled()) return;
- $this->settle_due(20);
- }
private function settle_one(int $id, string $operation_key = '') {
global $wpdb; $table = $this->table('commission_ledger');
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$table} WHERE id=%d", $id), ARRAY_A);
@@ -241,7 +233,6 @@ final class CommissionLedger {
return $negative ? -$minor : $minor;
}
public function overview(int $user_id): array {
- $this->lazy_settle_due();
global $wpdb; $table=$this->table('commission_ledger');
$row=$wpdb->get_row($wpdb->prepare("SELECT COALESCE(SUM(original_amount+adjustment_total),0) total, COALESCE(SUM(CASE WHEN status='pending' THEN original_amount+adjustment_total ELSE 0 END),0) pending, COALESCE(SUM(CASE WHEN status='settled' THEN original_amount+adjustment_total ELSE 0 END),0) settled FROM {$table} WHERE promoter_id=%d",$user_id),ARRAY_A) ?: [];
$withdrawn = '0.00';
diff --git a/src/Payment/ReferralAttribution.php b/src/Payment/ReferralAttribution.php
index 98f6a91e..ebcbecfa 100644
--- a/src/Payment/ReferralAttribution.php
+++ b/src/Payment/ReferralAttribution.php
@@ -291,24 +291,6 @@ final class ReferralAttribution {
return $bound > 0 && $bound !== $user_id ? $bound : 0;
}
- public function stats(int $promoter_id): array {
- global $wpdb;
- $table = \AKSRT\Core\DBMigrator::table('affiliate_relation');
- if ($promoter_id <= 0 || $table === '') {
- return ['promoters' => 0, 'effective_payers' => 0];
- }
- $promoters = (int) $wpdb->get_var($wpdb->prepare(
- "SELECT COUNT(*) FROM {$table} WHERE promoter_id = %d AND status = 'bound'",
- $promoter_id
- ));
- $ledger = \AKSRT\Core\DBMigrator::table('commission_ledger');
- $payers = $ledger !== '' ? (int) $wpdb->get_var($wpdb->prepare(
- "SELECT COUNT(DISTINCT source_user_id) FROM {$ledger} WHERE promoter_id = %d AND original_amount > 0",
- $promoter_id
- )) : 0;
- return ['promoters' => $promoters, 'effective_payers' => $payers];
- }
-
/** @return array{items:array>,total:int,page:int,per_page:int,max_pages:int} */
public function list_referred_users(int $promoter_id, int $page = 1, int $per_page = 10): array {
global $wpdb;
@@ -361,10 +343,8 @@ final class ReferralAttribution {
}
public function rest_me(): \WP_REST_Response {
- \AKSRT\Payment\CommissionLedger::get_instance()->lazy_settle_due();
$user_id = get_current_user_id();
$link = $this->create_link($user_id);
- $stats = $this->stats($user_id);
$affiliate = \AKSRT\Settings\Settings::get_affiliate_settings();
$registration = $this->registration_settings();
$reward = $this->reward_settings();
@@ -386,7 +366,6 @@ final class ReferralAttribution {
'window_days' => (int) ($this->shared_settings()['window_days'] ?? 30),
'settlement_days' => (int) ($affiliate['settlement_days'] ?? 7),
],
- 'stats' => $stats,
], 200);
}
diff --git a/tests/widget-system/referral-policy-contract.test.cjs b/tests/widget-system/referral-policy-contract.test.cjs
index 5eda4202..fa4a055d 100644
--- a/tests/widget-system/referral-policy-contract.test.cjs
+++ b/tests/widget-system/referral-policy-contract.test.cjs
@@ -61,19 +61,19 @@ test('commission base is the discounted payable amount, not the list price', ()
assert.match(ledger, /AssetAmount::balance\(is_string\(\$order\['amount'\] \?\? null\) \? \$order\['amount'\] : ''\)/)
})
-test('lazy settlement backstop guards the earnings entry points', () => {
+test('settlement stays off the read path and runs on the cron schedule', () => {
const ledger = read('src', 'Payment', 'CommissionLedger.php')
const attribution = read('src', 'Payment', 'ReferralAttribution.php')
- assert.match(ledger, /private static bool \$lazy_settled = false/)
- assert.match(ledger, /public function lazy_settle_due\(\): void/)
- assert.match(ledger, /if \(!\\AKSRT\\Settings\\Settings::affiliate_enabled\(\)\) return;/)
- assert.ok(
- ledger.indexOf('lazy_settle_due()') < ledger.indexOf('global \$wpdb; \$table=\$this->table\(\'commission_ledger\'\); \$row=\$wpdb->get_row'),
- 'overview() must trigger the backstop before aggregating balances'
- )
- // 收益总览与我的推广两个入口都接入了兜底。
- assert.match(attribution, /CommissionLedger::get_instance\(\)->lazy_settle_due\(\)/)
+ // 懒结算兜底已移除:读路径不得顺带写库(收益页打开慢的主因)。
+ assert.doesNotMatch(ledger, /lazy_settle_due|\$lazy_settled/)
+ // overview() 与 rest_me() 都不再触发结算。
+ assert.doesNotMatch(ledger, /\$this->lazy_settle_due\(\)/)
+ assert.doesNotMatch(attribution, /lazy_settle_due/)
+ assert.doesNotMatch(attribution, /->stats\(/)
+ // 结算只由 hourly 的 wp-cron 事件驱动。
+ assert.match(ledger, /add_action\('aksrt_affiliate_settle_due', \[\$this, 'settle_due'\]\)/)
+ assert.match(ledger, /wp_schedule_event\(time\(\) \+ HOUR_IN_SECONDS, 'hourly', 'aksrt_affiliate_settle_due'\)/)
})
test('referral codes are self-serve: no admin ops page, granted at registration', () => {
diff --git a/tests/widget-system/referral-qr-contract.test.cjs b/tests/widget-system/referral-qr-contract.test.cjs
index 551ec23a..8e9bc461 100644
--- a/tests/widget-system/referral-qr-contract.test.cjs
+++ b/tests/widget-system/referral-qr-contract.test.cjs
@@ -51,6 +51,21 @@ test('revenue detail card is a four-row info list with no standalone QR block',
assert.match(script, /data-affiliate-poster-download/)
assert.match(script, /openModal\(modal\)/)
assert.match(script, /link\.download = 'referral-poster\.png'/)
+ // 链接由 PHP 直出:复制按钮立即可用,REST 只负责海报配置与佣金比例。
+ assert.match(script, /var url = input \? input\.value : '';/)
+ assert.match(script, /if \(copy\) copy\.disabled = !url;/)
+ assert.doesNotMatch(script, /input\.value = j\.url/)
+})
+
+test('promotion link is server-rendered so it needs no round trip', () => {
+ const template = read('inc', 'helpers', 'user-center-renderers', 'profile-revenue.php')
+ // input 带服务端 value,不依赖 JS 回填。
+ assert.match(template, /data-affiliate-link value="<\?php echo esc_attr\(\$affiliate_link_url\); \?>"/)
+ assert.match(template, /create_link\(\$user_id\)\['url'\]/)
+ const attribution = read('src', 'Payment', 'ReferralAttribution.php')
+ // rest_me 不再做 COUNT 聚合,也不触发结算。
+ assert.doesNotMatch(attribution, /\$this->stats\(\$user_id\)/)
+ assert.doesNotMatch(attribution, /lazy_settle_due\(\)/)
})
test('poster canvas is built from admin-configured copy and gated on the poster switch', () => {
@@ -67,7 +82,7 @@ test('poster canvas is built from admin-configured copy and gated on the poster
const script = read('assets', 'js', 'credits.js')
// 海报仅在开关开启时暴露按钮;绘制含顶部图、文案换行、二维码与站点名。
- assert.match(script, /initAffiliatePoster\(root, j\.url \|\| '', j\.poster \|\| \{\}, j\.site_name \|\| document\.title\)/)
+ assert.match(script, /initAffiliatePoster\(root, url \|\| j\.url \|\| '', j\.poster \|\| \{\}, j\.site_name \|\| document\.title\)/)
assert.match(script, /poster\.enabled !== true\) return/)
assert.match(script, /function buildPosterCanvas/)
assert.match(script, /function wrapText\(ctx, text, maxWidth\)/)