feat(affiliate): attribution modes, settlement hardening, trusted proxies, QR and poster
- 归因模式可配置(first_touch/last_touch)、停佣开关、懒结算与佣金基数降级复查 - aksrt_get_client_ip 接入 aksrt_trusted_proxies 信任代理名单,防 CDN 头伪造 - 收益页 Canvas 二维码 + 750x1000 推广海报(本地打包 qrcode.min.js,无 CDN) - 契约测试固化归因/海报/信任代理链路
This commit is contained in:
1 parent
2618530afb
commit
20387fdb06
14 files changed
+715
-6
No files matched your search
@@ -57,6 +57,11 @@ final class CommissionLedger {
|
||||
if ($order_no === '' || $buyer <= 0 || $amount->minor() <= 0) return 0;
|
||||
$promoter = ReferralAttribution::get_instance()->resolve($buyer);
|
||||
if ($promoter <= 0) return 0;
|
||||
// 开关开启时,推广者的推广码已被禁用(或缺失)则不再产生新佣金。
|
||||
if (!empty($settings['disabled_code_stops_commission'])
|
||||
&& !ReferralAttribution::get_instance()->is_promoter_code_active($promoter)) {
|
||||
return 0;
|
||||
}
|
||||
// 返佣订单类型按推广者会员等级分档(参考主题:每档独立勾选)。
|
||||
$promoter_level = function_exists('aksrt_get_user_vip_level') ? (int) aksrt_get_user_vip_level($promoter) : 0;
|
||||
if (!in_array($order_type, \AKSRT\Settings\PaymentRuntime::affiliate_order_types_for_level($promoter_level), true)) return 0;
|
||||
@@ -98,6 +103,14 @@ 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);
|
||||
@@ -228,6 +241,7 @@ 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';
|
||||
|
||||
@@ -51,7 +51,12 @@ final class ReferralAttribution {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->cookie_ref() !== '') {
|
||||
$current = $this->cookie_ref();
|
||||
if ($current === $raw) {
|
||||
return;
|
||||
}
|
||||
// 首触模式:已有归因不覆盖;末触模式:最近一次点击覆盖之前的归因。
|
||||
if ($current !== '' && $this->attribution_model() !== 'last_touch') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -75,21 +80,28 @@ final class ReferralAttribution {
|
||||
$submitted_ref = sanitize_key($submitted_ref);
|
||||
$cookie_ref = $this->cookie_ref();
|
||||
$submitted_valid = $submitted_ref !== '' && $this->resolve_promoter($submitted_ref) > 0;
|
||||
$last_touch = $this->attribution_model() === 'last_touch';
|
||||
|
||||
$conflict = $cookie_ref !== '' && $submitted_ref !== '' && $submitted_ref !== $cookie_ref;
|
||||
if ($conflict) {
|
||||
self::$registration_notice = __('检测到不同的推广码,已按首次访问的推广关系绑定。', 'aksrt');
|
||||
self::$registration_notice = $last_touch
|
||||
? __('检测到不同的推广码,已按最近访问的推广关系绑定。', 'aksrt')
|
||||
: __('检测到不同的推广码,已按首次访问的推广关系绑定。', 'aksrt');
|
||||
}
|
||||
|
||||
$ref = $cookie_ref !== '' ? $cookie_ref : ($submitted_valid ? $submitted_ref : '');
|
||||
// 首触:cookie 优先;末触:注册时填写的有效推广码优先。
|
||||
$ref = $cookie_ref !== '' && !($last_touch && $submitted_valid)
|
||||
? $cookie_ref
|
||||
: ($submitted_valid ? $submitted_ref : $cookie_ref);
|
||||
self::$pending_registration_ref = $ref;
|
||||
|
||||
$source = $ref === '' ? 'none' : ($ref === $cookie_ref ? 'cookie' : 'submitted');
|
||||
return [
|
||||
'ref' => $ref,
|
||||
'valid' => $ref !== '',
|
||||
'submitted_valid' => $submitted_valid,
|
||||
'conflict' => $conflict,
|
||||
'source' => $cookie_ref !== '' ? 'cookie' : ($submitted_valid ? 'submitted' : 'none'),
|
||||
'source' => $source,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -377,6 +389,7 @@ 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);
|
||||
@@ -387,6 +400,8 @@ final class ReferralAttribution {
|
||||
'ref' => $link['ref'],
|
||||
'url' => $link['url'],
|
||||
'status' => $link['status'],
|
||||
'site_name' => get_bloginfo('name'),
|
||||
'poster' => (array) ($affiliate['poster'] ?? ['enabled' => false]),
|
||||
'features' => [
|
||||
'registration' => !empty($registration['enabled']),
|
||||
'points_reward' => !empty($reward['enabled']),
|
||||
@@ -612,7 +627,19 @@ final class ReferralAttribution {
|
||||
private function shared_settings(): array {
|
||||
return class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::get_referral_settings()
|
||||
: ['code_length' => 12, 'window_days' => 30];
|
||||
: ['code_length' => 12, 'window_days' => 30, 'attribution_model' => 'first_touch'];
|
||||
}
|
||||
|
||||
private function attribution_model(): string {
|
||||
return (string) ($this->shared_settings()['attribution_model'] ?? 'first_touch');
|
||||
}
|
||||
|
||||
/** 推广者的推广码当前是否处于激活状态(码缺失视为未激活)。 */
|
||||
public function is_promoter_code_active(int $promoter_id): bool {
|
||||
if ($promoter_id <= 0) {
|
||||
return false;
|
||||
}
|
||||
return $this->code_record($promoter_id)['status'] === self::STATUS_ACTIVE;
|
||||
}
|
||||
|
||||
private function registration_settings(): array {
|
||||
|
||||
Reference in new issue
Block a user