Merge pull request 'perf(revenue): server-render the promotion link and stop settling on reads' (#37) from worktree-revenue-card-rework into main
Reviewed-on: #37
This commit was merged in pull request #37.
This commit is contained in:
@@ -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('推广链接已复制。'); };
|
||||
|
||||
@@ -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 = [
|
||||
<div class="uc-revenue-summary-list">
|
||||
<div class="uc-revenue-summary-row uc-revenue-summary-row--link">
|
||||
<span><?php esc_html_e('推广链接', 'aksrt'); ?></span>
|
||||
<input type="text" readonly data-affiliate-link aria-label="<?php esc_attr_e('推广链接', 'aksrt'); ?>">
|
||||
<input type="text" readonly data-affiliate-link value="<?php echo esc_attr($affiliate_link_url); ?>" aria-label="<?php esc_attr_e('推广链接', 'aksrt'); ?>">
|
||||
<button type="button" class="btn-primary btn-sm" data-affiliate-copy><?php esc_html_e('复制链接', 'aksrt'); ?></button>
|
||||
<?php if (!empty($affiliate['poster']['enabled'])) : ?><button type="button" class="btn-outline btn-sm" data-affiliate-poster hidden><?php esc_html_e('推广海报', 'aksrt'); ?></button><?php endif; ?>
|
||||
<small data-affiliate-feedback role="status" aria-live="polite"></small>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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<int,array<string,mixed>>,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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\)/)
|
||||
|
||||
Reference in New Issue
Block a user