feat: adjust affiliate commissions and withdrawal strategy

This commit is contained in:
2026-08-16 23:11:01 +08:00
parent 8fc6fe5312
commit 929603b9e2
13 changed files with 93 additions and 111 deletions
@@ -2,22 +2,16 @@ export const affiliateSchema = {
key: 'payment.affiliate',
title: '推广返佣',
icon: 'fas fa-share-alt',
description: '配置推广归因、佣金比例、结算和提现规则。',
description: '配置推广归因、分级佣金比例和结算规则。',
defaults: {
affiliate_enabled: false,
affiliate_ratio: 5,
affiliate_ratio_user: 5,
affiliate_ratio_vip_1: 5,
affiliate_ratio_vip_2: 5,
affiliate_order_types: ['membership', 'content'],
affiliate_touch_window_days: 30,
affiliate_settlement_days: 7,
affiliate_allow_self_purchase: false,
affiliate_show_stats: true,
affiliate_promotion_text: '',
affiliate_payout_channels: ['alipay', 'wechat'],
affiliate_min_withdrawal: 50,
affiliate_withdrawal_fee: 5,
affiliate_daily_withdrawal_count: 3,
affiliate_daily_withdrawal_limit: 500,
affiliate_withdrawal_reset_time: '00:00',
},
groups: [
{
@@ -26,25 +20,12 @@ export const affiliateSchema = {
icon: 'fas fa-project-diagram',
fields: [
{ key: 'affiliate_enabled', label: '启用推广返佣', type: 'switch', default: false },
{ key: 'affiliate_ratio', label: '全站佣金比例(%)', type: 'number', default: 5, min: 0, max: 100, dependsOn: { key: 'affiliate_enabled', value: true } },
{ key: 'affiliate_order_types', label: '适用订单类型', type: 'checkbox', default: ['membership', 'content'], sanitize_type: 'array', options: [{ label: '会员订单', value: 'membership' }, { label: '付费内容', value: 'content' }], dependsOn: { key: 'affiliate_enabled', value: true } },
{ key: 'affiliate_ratio_user', label: '普通会员佣金比例(%)', type: 'number', default: 5, min: 0, max: 100, dependsOn: { key: 'affiliate_enabled', value: true } },
{ key: 'affiliate_ratio_vip_1', label: '一级会员佣金比例(%)', type: 'number', default: 5, min: 0, max: 100, dependsOn: { key: 'affiliate_enabled', value: true } },
{ key: 'affiliate_ratio_vip_2', label: '二级会员佣金比例(%)', type: 'number', default: 5, min: 0, max: 100, dependsOn: { key: 'affiliate_enabled', value: true } },
{ key: 'affiliate_order_types', label: '适用订单类型', type: 'checkbox', default: ['membership', 'content'], sanitize_type: 'array', options: [{ label: '会员订单', value: 'membership' }, { label: '付费内容', value: 'content' }, { label: '购买积分', value: 'credits' }], dependsOn: { key: 'affiliate_enabled', value: true } },
{ key: 'affiliate_touch_window_days', label: '首次触达窗口(天)', type: 'number', default: 30, min: 1, max: 365, dependsOn: { key: 'affiliate_enabled', value: true } },
{ key: 'affiliate_settlement_days', label: '结算观察期(天)', type: 'number', default: 7, min: 0, max: 90, dependsOn: { key: 'affiliate_enabled', value: true } },
{ key: 'affiliate_allow_self_purchase', label: '允许自购返佣', type: 'switch', default: false, dependsOn: { key: 'affiliate_enabled', value: true } },
{ key: 'affiliate_show_stats', label: '展示推广统计', type: 'switch', default: true, dependsOn: { key: 'affiliate_enabled', value: true } },
],
},
{
key: 'withdrawal',
title: '提现规则',
icon: 'fas fa-wallet',
fields: [
{ key: 'affiliate_min_withdrawal', label: '最低提现金额', type: 'number', default: 50, min: 0, max: 99999, dependsOn: { key: 'affiliate_enabled', value: true } },
{ key: 'affiliate_withdrawal_fee', label: '提现手续费(%)', type: 'number', default: 5, min: 0, max: 100, dependsOn: { key: 'affiliate_enabled', value: true } },
{ key: 'affiliate_daily_withdrawal_count', label: '每日可提现次数', type: 'number', default: 3, min: 0, max: 99999, dependsOn: { key: 'affiliate_enabled', value: true } },
{ key: 'affiliate_daily_withdrawal_limit', label: '每日提现上限', type: 'number', default: 500, min: 0, max: 999999, dependsOn: { key: 'affiliate_enabled', value: true } },
{ key: 'affiliate_withdrawal_reset_time', label: '重置时间', type: 'input', default: '00:00', help: '按站点 WordPress 时区重置,格式 HH:MM。', dependsOn: { key: 'affiliate_enabled', value: true } },
{ key: 'affiliate_payout_channels', label: '可用收款渠道', type: 'checkbox', default: ['alipay', 'wechat'], sanitize_type: 'array', options: [{ label: '支付宝收款码', value: 'alipay' }, { label: '微信收款码', value: 'wechat' }], dependsOn: { key: 'affiliate_enabled', value: true } },
],
},
{
@@ -1,15 +1,15 @@
export const withdrawalSchema = {
key: 'payment.withdrawal',
title: '提现管理',
title: '提现策略',
icon: 'fas fa-hand-holding-usd',
description: '配置提现门槛、手续费比例与每日限制。',
defaults: {
pay_balance_withdraw_s: false,
pay_rebate_withdraw_lowest_money: 50,
withdraw_service_charge: 5,
withdraw_daily_count: 3,
withdraw_daily_limit: 50,
withdraw_reset_time: '00:00',
affiliate_min_withdrawal: 50,
affiliate_withdrawal_fee: 5,
affiliate_daily_withdrawal_count: 3,
affiliate_daily_withdrawal_limit: 500,
affiliate_withdrawal_reset_time: '00:00',
affiliate_payout_channels: ['alipay', 'wechat'],
},
groups: [
{
@@ -17,12 +17,12 @@ export const withdrawalSchema = {
title: '提现设置',
icon: 'fas fa-sliders-h',
fields: [
{ key: 'pay_balance_withdraw_s', label: '启用余额提现', type: 'switch', default: false },
{ key: 'pay_rebate_withdraw_lowest_money', label: '最低提现金额', type: 'number', default: 50, min: 0, max: 99999, dependsOn: { key: 'pay_balance_withdraw_s', value: true } },
{ key: 'withdraw_service_charge', label: '提现手续费(%)', type: 'number', default: 5, min: 0, max: 50, dependsOn: { key: 'pay_balance_withdraw_s', value: true } },
{ key: 'withdraw_daily_count', label: '每日可提现次数', type: 'number', default: 3, min: 0, max: 99999, dependsOn: { key: 'pay_balance_withdraw_s', value: true } },
{ key: 'withdraw_daily_limit', label: '每日提现上限', type: 'number', default: 50, min: 0, max: 999999, dependsOn: { key: 'pay_balance_withdraw_s', value: true } },
{ key: 'withdraw_reset_time', label: '重置时间', type: 'input', default: '00:00', help: '按站点 WordPress 时区重置,格式 HH:MM。', dependsOn: { key: 'pay_balance_withdraw_s', value: true } },
{ key: 'affiliate_min_withdrawal', label: '最低提现金额', type: 'number', default: 50, min: 0, max: 99999 },
{ key: 'affiliate_withdrawal_fee', label: '提现手续费(%)', type: 'number', default: 5, min: 0, max: 100 },
{ key: 'affiliate_daily_withdrawal_count', label: '每日可提现次数', type: 'number', default: 3, min: 0, max: 99999 },
{ key: 'affiliate_daily_withdrawal_limit', label: '每日提现上限', type: 'number', default: 500, min: 0, max: 999999 },
{ key: 'affiliate_withdrawal_reset_time', label: '重置时间', type: 'input', default: '00:00', help: '按站点 WordPress 时区重置,格式 HH:MM。' },
{ key: 'affiliate_payout_channels', label: '可用收款渠道', type: 'checkbox', default: ['alipay', 'wechat'], sanitize_type: 'array', options: [{ label: '支付宝收款码', value: 'alipay' }, { label: '微信收款码', value: 'wechat' }] },
],
},
],
+1 -1
View File
@@ -209,7 +209,7 @@
input.style.width = Math.max(28, Math.min(96, linkLength || 28)) + 'ch';
}
if (copy) copy.disabled = !j.url;
var ratio = root.querySelector('[data-affiliate-ratio]'); if (ratio) ratio.textContent = String(j.ratio || 0) + '%';
var ratio = root.querySelector('[data-affiliate-ratio]'); if (ratio) ratio.textContent = String(j.current_ratio || 0) + '%';
var promoters = root.querySelector('[data-affiliate-promoters]'); if (promoters) promoters.textContent = String(stats.promoters || 0);
var payers = root.querySelector('[data-affiliate-payers]'); if (payers) payers.textContent = String(stats.effective_payers || 0);
}).catch(function () { setFeedback('推广数据加载失败。', true); });
+1 -13
View File
@@ -606,19 +606,7 @@ if (!function_exists('aksrt_get_post_rebate_discount')) {
return 0.0;
}
// Need a positive ratio for referrer (VIP tier of referrer preferred).
$ratio = (int) ($aff['ratio'] ?? 0);
$user_ratio = is_array($aff['user_ratio'] ?? null) ? $aff['user_ratio'] : [];
$ref_vip = 0;
if (function_exists('aksrt_get_user_vip_level')) {
$ref_vip = (int) aksrt_get_user_vip_level($referrer_id);
}
if ($ref_vip >= 2 && !empty($user_ratio['vip2'])) {
$ratio = (int) $user_ratio['vip2'];
} elseif ($ref_vip >= 1 && !empty($user_ratio['vip1'])) {
$ratio = (int) $user_ratio['vip1'];
} elseif (isset($user_ratio['user'])) {
$ratio = (int) $user_ratio['user'];
}
$ratio = (int) round(\AKSRT\Settings\Settings::affiliate_ratio_for_user($referrer_id));
if ($ratio <= 0) {
return 0.0;
}
@@ -26,7 +26,7 @@ $withdraw_on = !empty($affiliate['enabled'])
if ($withdraw_on && class_exists('\AKSRT\Settings\Capabilities') && !\AKSRT\Settings\Capabilities::user_can('balance_withdraw', $user_id)) {
$withdraw_on = false;
}
$withdraw_min = max(1, (float) ($affiliate['min_withdrawal'] ?? $withdrawal['min_amount'] ?? 50));
$withdraw_min = max(1, (float) ($withdrawal['min_amount'] ?? 50));
$wallet_fmt = class_exists('\AKSRT\Credits\Balance')
? \AKSRT\Credits\Balance::format((float) \AKSRT\Credits\Balance::balance($user_id))
: '0.00';
@@ -38,7 +38,9 @@ $available_commission = class_exists('\AKSRT\Credits\Balance')
? (float) \AKSRT\Credits\Balance::balance($user_id)
: (float) ($commission_stats['settled'] ?? 0);
$vip_ratios = [];
$commission_ratio = (float) ($affiliate['ratio'] ?? 0);
$commission_ratio = class_exists('\AKSRT\Settings\Settings')
? (float) \AKSRT\Settings\Settings::affiliate_ratio_for_user($user_id)
: 0.0;
$details_text = trim((string) ($affiliate['promotion_text'] ?? ''));
if ($details_text === '') {
$details_text = __('好友通过推广链接完成符合条件的订单后,系统会按当前佣金比例记录返佣,结算后进入可提现余额。', 'aksrt');
@@ -9,6 +9,10 @@ $GLOBALS['aksrt_withdraw_modal_rendered'] = true;
$withdraw_min = max(0, (float) ($withdraw_min ?? 50));
$withdraw_usage = is_array($withdraw_usage ?? null) ? $withdraw_usage : ['remaining_count' => 0, 'remaining_amount' => 0];
$withdraw_fee_rate = (int) ($withdraw_settings['service_charge_rate'] ?? 5);
$withdraw_channels = array_values(array_intersect(['alipay', 'wechat'], (array) ($withdraw_settings['payout_channels'] ?? ['alipay', 'wechat'])));
if (!$withdraw_channels) {
$withdraw_channels = ['alipay', 'wechat'];
}
?>
<div class="uc-modal" id="aksrt-withdraw-modal" hidden data-aksrt-withdraw-modal>
<div class="uc-modal__overlay" data-aksrt-withdraw-close></div>
@@ -21,8 +25,9 @@ $withdraw_fee_rate = (int) ($withdraw_settings['service_charge_rate'] ?? 5);
<p class="muted-2-color em09 mt6"><?php printf(esc_html__('最低提现%s 元,今日可提现%d次,提现手续费率%d%%', 'aksrt'), esc_html((string) $withdraw_min), (int) ($withdraw_usage['remaining_count'] ?? 0), $withdraw_fee_rate); ?></p>
<label class="muted-color em09 mt10"><?php esc_html_e('收款方式', 'aksrt'); ?></label>
<select class="uc-card-pass__input" name="method" data-aksrt-withdraw-method>
<option value="alipay"><?php esc_html_e('支付宝', 'aksrt'); ?></option>
<option value="wechat"><?php esc_html_e('微信', 'aksrt'); ?></option>
<?php foreach ($withdraw_channels as $channel) : ?>
<option value="<?php echo esc_attr($channel); ?>"><?php echo esc_html($channel === 'wechat' ? __('微信收款码', 'aksrt') : __('支付宝收款码', 'aksrt')); ?></option>
<?php endforeach; ?>
</select>
<label class="muted-color em09 mt10"><?php esc_html_e('收款账户信息', 'aksrt'); ?></label>
<textarea class="uc-card-pass__input" name="account" rows="3" data-aksrt-withdraw-account placeholder="<?php esc_attr_e('姓名 + 账户 / 卡号', 'aksrt'); ?>"></textarea>
+6 -13
View File
@@ -143,23 +143,15 @@ class SchemaPayment {
'groups' => [
['key' => 'policy', 'title' => '返佣策略', 'fields' => [
['key' => 'affiliate_enabled', 'label' => '启用推广返佣', 'type' => 'switch', 'default' => false],
['key' => 'affiliate_ratio', 'label' => '全站佣金比例(%)', 'type' => 'number', 'default' => 5, 'min' => 0, 'max' => 100],
['key' => 'affiliate_order_types', 'label' => '适用订单类型', 'type' => 'checkbox', 'options' => [['value' => 'membership', 'label' => '会员订单'], ['value' => 'content', 'label' => '付费内容']], 'default' => ['membership', 'content']],
['key' => 'affiliate_ratio_user', 'label' => '普通会员佣金比例(%)', 'type' => 'number', 'default' => 5, 'min' => 0, 'max' => 100],
['key' => 'affiliate_ratio_vip_1', 'label' => '一级会员佣金比例(%)', 'type' => 'number', 'default' => 5, 'min' => 0, 'max' => 100],
['key' => 'affiliate_ratio_vip_2', 'label' => '二级会员佣金比例(%)', 'type' => 'number', 'default' => 5, 'min' => 0, 'max' => 100],
['key' => 'affiliate_order_types', 'label' => '适用订单类型', 'type' => 'checkbox', 'options' => [['value' => 'membership', 'label' => '会员订单'], ['value' => 'content', 'label' => '付费内容'], ['value' => 'credits', 'label' => '购买积分']], 'default' => ['membership', 'content']],
['key' => 'affiliate_touch_window_days', 'label' => '首次触达窗口(天)', 'type' => 'number', 'default' => 30, 'min' => 1, 'max' => 365],
['key' => 'affiliate_settlement_days', 'label' => '结算观察期(天)', 'type' => 'number', 'default' => 7, 'min' => 0, 'max' => 90],
['key' => 'affiliate_allow_self_purchase', 'label' => '允许自购返佣', 'type' => 'switch', 'default' => false],
['key' => 'affiliate_show_stats', 'label' => '展示推广统计', 'type' => 'switch', 'default' => true],
]],
['key' => 'withdrawal', 'title' => '提现规则', 'fields' => [
['key' => 'affiliate_min_withdrawal', 'label' => '最低提现金额', 'type' => 'number', 'default' => 50, 'min' => 0, 'max' => 99999],
['key' => 'affiliate_withdrawal_fee', 'label' => '提现手续费(%)', 'type' => 'number', 'default' => 5, 'min' => 0, 'max' => 100],
['key' => 'affiliate_daily_withdrawal_count', 'label' => '每日可提现次数', 'type' => 'number', 'default' => 3, 'min' => 0, 'max' => 99999],
['key' => 'affiliate_daily_withdrawal_limit', 'label' => '每日提现上限', 'type' => 'number', 'default' => 500, 'min' => 0, 'max' => 999999],
['key' => 'affiliate_withdrawal_reset_time', 'label' => '重置时间', 'type' => 'input', 'default' => '00:00'],
]],
['key' => 'text', 'title' => '文案设置', 'fields' => [
['key' => 'affiliate_promotion_text', 'label' => '推广说明', 'type' => 'textarea', 'default' => ''],
['key' => 'affiliate_payout_channels', 'label' => '可用收款渠道', 'type' => 'checkbox', 'options' => [['value' => 'alipay', 'label' => '支付宝收款码'], ['value' => 'wechat', 'label' => '微信收款码']], 'default' => ['alipay', 'wechat']],
]],
],
],
@@ -317,7 +309,7 @@ class SchemaPayment {
],
'withdrawal' => [
'key' => 'withdrawal',
'title' => '提现管理',
'title' => '提现策略',
'version' => '1.0.0',
'option_key' => 'aksrt_settings',
'groups' => [
@@ -327,6 +319,7 @@ class SchemaPayment {
['key' => 'affiliate_daily_withdrawal_count', 'label' => '每日可提现次数', 'type' => 'number', 'default' => 3, 'min' => 0, 'max' => 99999],
['key' => 'affiliate_daily_withdrawal_limit', 'label' => '每日提现上限', 'type' => 'number', 'default' => 500, 'min' => 0, 'max' => 999999],
['key' => 'affiliate_withdrawal_reset_time', 'label' => '重置时间', 'type' => 'input', 'default' => '00:00', 'help' => '按站点 WordPress 时区重置,格式 HH:MM。'],
['key' => 'affiliate_payout_channels', 'label' => '可用收款渠道', 'type' => 'checkbox', 'options' => [['value' => 'alipay', 'label' => '支付宝收款码'], ['value' => 'wechat', 'label' => '微信收款码']], 'default' => ['alipay', 'wechat']],
]],
],
],
+1 -14
View File
@@ -383,20 +383,7 @@ trait PayHelpers {
return $empty;
}
$ratio = (float) ($aff['ratio'] ?? 0);
$user_ratio = is_array($aff['user_ratio'] ?? null) ? $aff['user_ratio'] : [];
$ref_vip = 0;
if (function_exists('aksrt_get_user_vip_level')) {
$ref_vip = (int) aksrt_get_user_vip_level($referrer_id);
}
if ($ref_vip >= 2 && isset($user_ratio['vip2']) && (float) $user_ratio['vip2'] > 0) {
$ratio = (float) $user_ratio['vip2'];
} elseif ($ref_vip >= 1 && isset($user_ratio['vip1']) && (float) $user_ratio['vip1'] > 0) {
$ratio = (float) $user_ratio['vip1'];
} elseif (isset($user_ratio['user'])) {
$ratio = (float) $user_ratio['user'];
}
$ratio = max(0.0, min(100.0, $ratio));
$ratio = (float) \AKSRT\Settings\Settings::affiliate_ratio_for_user($referrer_id);
if ($ratio <= 0) {
return $empty;
}
+6 -1
View File
@@ -338,6 +338,7 @@ trait BalanceServiceRest {
if (is_wp_error($refund)) return new \WP_Error('aksrt_exchange_recovery_failed', __('积分入账失败,余额退回失败,请联系管理员', 'aksrt'), ['status' => 500]);
return $add;
}
do_action('aksrt_points_exchange_completed', $user_id, (float) $amount, $points, (string) $exchange_ref);
return rest_ensure_response(['success' => true, 'data' => [
'message' => sprintf(__('兑换成功,获得 %d 积分', 'aksrt'), $points),
'wallet_balance' => Balance::balance($user_id),
@@ -387,6 +388,7 @@ trait BalanceServiceRest {
}
$amount = (int) $raw_amount;
$method = sanitize_key((string) ($params['method'] ?? 'alipay'));
$method = $method !== '' ? $method : 'alipay';
$account = sanitize_textarea_field((string) ($params['account_info'] ?? $params['account'] ?? ''));
$withdraw_settings = class_exists('\\AKSRT\\Settings\\Settings')
? \AKSRT\Settings\Settings::get_withdrawal_settings()
@@ -398,7 +400,10 @@ trait BalanceServiceRest {
if ($account === '') {
return new \WP_Error('aksrt_withdraw_account', __('请填写收款账号信息', 'aksrt'), ['status' => 400]);
}
$result = \AKSRT\Payment\Withdrawal::get_instance()->create_balance_request($user_id, $amount, $method !== '' ? $method : 'alipay', $account, $withdraw_settings);
if (!in_array($method, (array) ($withdraw_settings['payout_channels'] ?? []), true)) {
return new \WP_Error('aksrt_withdraw_channel', __('当前收款渠道不可用', 'aksrt'), ['status' => 400]);
}
$result = \AKSRT\Payment\Withdrawal::get_instance()->create_balance_request($user_id, $amount, $method, $account, $withdraw_settings);
if (is_wp_error($result)) {
return $result;
}
+19 -6
View File
@@ -12,6 +12,7 @@ final class CommissionLedger {
public function init(): void {
add_action('aksrt_order_paid', [$this, 'on_order_paid'], 20, 2);
add_action('aksrt_order_status_changed', [$this, 'on_order_status_changed'], 20, 3);
add_action('aksrt_points_exchange_completed', [$this, 'on_points_exchange_completed'], 20, 4);
add_action('aksrt_affiliate_settle_due', [$this, 'settle_due']);
add_action('rest_api_init', [$this, 'register_routes']);
if (!wp_next_scheduled('aksrt_affiliate_settle_due')) {
@@ -23,6 +24,16 @@ final class CommissionLedger {
public function on_order_status_changed(string $order_number, string $status, array $order = []): void {
if (in_array($status, ['refunding', 'refunded'], true)) { $this->reverse($order_number, (float) ($order['amount'] ?? 0), 'refund'); }
}
public function on_points_exchange_completed(int $user_id, float $amount, int $points, string $exchange_ref): void {
$this->record_paid_order([
'order_number' => 'points-exchange-' . sanitize_key($exchange_ref),
'product_type' => 'credits',
'user_id' => $user_id,
'amount' => $amount,
'exchange_ref' => $exchange_ref,
'points' => $points,
]);
}
public function record_paid_order(array $order): int {
if (!\AKSRT\Settings\Settings::affiliate_enabled()) return 0;
$type = sanitize_key((string) ($order['product_type'] ?? ''));
@@ -35,14 +46,16 @@ final class CommissionLedger {
$amount = round((float) ($order['amount'] ?? 0), 2);
if ($order_no === '' || $buyer <= 0 || $amount <= 0) return 0;
$promoter = ReferralAttribution::get_instance()->resolve($buyer);
if ($promoter <= 0 || $promoter === $buyer) return 0;
if (!$settings['allow_self_purchase'] && $promoter === $buyer) return 0;
$ratio = (float) $settings['ratio'];
if ($promoter <= 0) return 0;
$ratio = (float) \AKSRT\Settings\PaymentRuntime::affiliate_ratio_for_user($promoter);
$commission = round($amount * $ratio / 100, 2);
if ($commission <= 0) return 0;
global $wpdb;
$table = $this->table('commission_ledger');
$key = hash('sha256', 'affiliate:' . $order_no . ':' . $order_type);
$identity = $order_type === 'credits' && !empty($order['exchange_ref'])
? 'credits:' . sanitize_key((string) $order['exchange_ref'])
: $order_no . ':' . $order_type;
$key = hash('sha256', 'affiliate:' . $identity);
$now = current_time('mysql', true);
$available = gmdate('Y-m-d H:i:s', time() + ((int) $settings['settlement_days'] * DAY_IN_SECONDS));
$ok = $wpdb->insert($table, ['promoter_id'=>$promoter,'source_user_id'=>$buyer,'order_number'=>$order_no,'order_type'=>$order_type,'eligible_base'=>$amount,'ratio'=>$ratio,'original_amount'=>$commission,'adjustment_total'=>0,'status'=>'pending','idempotency_key'=>$key,'available_at'=>$available,'created_at'=>$now], ['%d','%d','%s','%s','%f','%f','%f','%f','%s','%s','%s','%s']);
@@ -123,9 +136,9 @@ final class CommissionLedger {
$user_id=get_current_user_id(); $amount=(int)round((float)$request->get_param('amount')); $method=sanitize_key((string)$request->get_param('method')); $account=sanitize_text_field((string)$request->get_param('account')); $settings=\AKSRT\Settings\Settings::get_withdrawal_settings(); $affiliate=\AKSRT\Settings\Settings::get_affiliate_settings();
if (empty($affiliate['enabled'])) return new \WP_Error('affiliate_disabled',__('推广返佣未启用。','aksrt'),['status'=>403]);
if (\AKSRT\Payment\Withdrawal::get_instance()->has_open_withdrawal($user_id)) return new \WP_Error('affiliate_withdraw_pending',__('已有待处理提现申请。','aksrt'),['status'=>409]);
if ($amount < (int)ceil((float)$affiliate['min_withdrawal'])) return new \WP_Error('affiliate_withdraw_min',__('未达到最低提现金额。','aksrt'),['status'=>400]);
if ($amount < (int)ceil((float)$settings['min_amount'])) return new \WP_Error('affiliate_withdraw_min',__('未达到最低提现金额。','aksrt'),['status'=>400]);
if ((float)get_user_meta($user_id,self::DEBT_META,true)>0) return new \WP_Error('affiliate_withdraw_debt',__('存在待偿还的佣金欠款,暂不可提现。','aksrt'),['status'=>409]);
if (!in_array($method,(array)$affiliate['payout_channels'],true) || $account==='') return new \WP_Error('affiliate_withdraw_account',__('请提供有效的收款渠道和收款码。','aksrt'),['status'=>400]);
if (!in_array($method,(array)$settings['payout_channels'],true) || $account==='') return new \WP_Error('affiliate_withdraw_account',__('请提供有效的收款渠道和收款码。','aksrt'),['status'=>400]);
$result=\AKSRT\Payment\Withdrawal::get_instance()->create_balance_request($user_id,$amount,$method,$account,$settings); if(is_wp_error($result)) return $result; return new \WP_REST_Response(['success'=>true,'data'=>$result],201);
}
public function admin_withdrawals(array $args): array { $service=\AKSRT\Payment\Withdrawal::get_instance(); return ['items'=>$service->get_all(['status'=>sanitize_key((string)($args['status']??'')),'limit'=>max(1,min(100,(int)($args['per_page']??20))),'offset'=>max(0,((int)($args['page']??1)-1)*max(1,(int)($args['per_page']??20)))]),'stats'=>$service->get_stats()]; }
+2 -1
View File
@@ -197,7 +197,8 @@ final class ReferralAttribution {
return new \WP_REST_Response([
'code' => $link['code'],
'url' => $link['url'],
'ratio' => (float) ($settings['ratio'] ?? 5),
'current_ratio' => (float) \AKSRT\Settings\Settings::affiliate_ratio_for_user($uid),
'ratios' => (array) ($settings['ratios'] ?? []),
'order_types' => (array) ($settings['order_types'] ?? ['membership', 'content']),
'rules' => ['window_days' => (int) ($settings['window_days'] ?? 30), 'settlement_days' => (int) ($settings['settlement_days'] ?? 7)],
'stats' => $stats,
+3 -2
View File
@@ -176,14 +176,15 @@ trait CreditsPaymentGroups {
}
/**
* @return array{enabled:bool,ratio:int,window_days:int,require_paid_order:bool,note:string}
* @return array<string,mixed>
*/
public static function get_payment_affiliate_settings(): array {
return PaymentRuntime::get_payment_affiliate_settings();
}
public static function affiliate_enabled(): bool { return PaymentRuntime::affiliate_enabled(); }
public static function affiliate_ratio(): float { return PaymentRuntime::affiliate_ratio(); }
public static function affiliate_ratio_for_user(int $user_id = 0): float { return PaymentRuntime::affiliate_ratio_for_user($user_id > 0 ? $user_id : get_current_user_id()); }
public static function affiliate_ratio_for_level(int $level): float { return PaymentRuntime::affiliate_ratio_for_level($level); }
public static function get_affiliate_settings(): array { return PaymentRuntime::get_affiliate_settings(); }
public static function get_settlement_settings(): array { return PaymentRuntime::get_settlement_settings(); }
+22 -16
View File
@@ -282,30 +282,34 @@ class PaymentRuntime {
*/
public static function get_payment_affiliate_settings(): array {
$settings = Settings::get_all();
$ratio = max(0, min(100, (float) ($settings['affiliate_ratio'] ?? 5)));
$fee = max(0, min(100, (float) ($settings['affiliate_withdrawal_fee'] ?? 5)));
$clamp_ratio = static fn ($value): float => max(0, min(100, (float) $value));
$types = (array) ($settings['affiliate_order_types'] ?? ['membership', 'content']);
$types = array_values(array_intersect(['membership', 'content'], array_map('sanitize_key', $types)));
$types = array_values(array_intersect(['membership', 'content', 'credits'], array_map('sanitize_key', $types)));
if (!$types) { $types = ['membership', 'content']; }
$channels = array_values(array_intersect(['alipay', 'wechat'], array_map('sanitize_key', (array) ($settings['affiliate_payout_channels'] ?? ['alipay', 'wechat']))));
if (!$channels) { $channels = ['alipay', 'wechat']; }
return [
'enabled' => !empty($settings['affiliate_enabled']),
'ratio' => $ratio,
'ratios' => [
'user' => $clamp_ratio($settings['affiliate_ratio_user'] ?? 5),
'vip_1' => $clamp_ratio($settings['affiliate_ratio_vip_1'] ?? 5),
'vip_2' => $clamp_ratio($settings['affiliate_ratio_vip_2'] ?? 5),
],
'order_types' => $types,
'window_days' => max(1, min(365, (int) ($settings['affiliate_touch_window_days'] ?? 30))),
'settlement_days' => max(0, min(90, (int) ($settings['affiliate_settlement_days'] ?? 7))),
'min_withdrawal' => max(0, min(999999, (float) ($settings['affiliate_min_withdrawal'] ?? 50))),
'withdrawal_fee' => $fee,
'payout_channels' => $channels,
'allow_self_purchase' => !empty($settings['affiliate_allow_self_purchase']),
'show_stats' => !isset($settings['affiliate_show_stats']) || !empty($settings['affiliate_show_stats']),
'promotion_text' => sanitize_textarea_field((string) ($settings['affiliate_promotion_text'] ?? '')),
];
}
public static function affiliate_enabled(): bool { return !empty(self::get_payment_affiliate_settings()['enabled']); }
public static function affiliate_ratio(): float { return (float) self::get_payment_affiliate_settings()['ratio']; }
public static function affiliate_ratio_for_user(int $user_id): float {
$level = function_exists('aksrt_get_user_vip_level') ? (int) aksrt_get_user_vip_level($user_id) : 0;
return self::affiliate_ratio_for_level($level);
}
public static function affiliate_ratio_for_level(int $level): float {
$settings = self::get_payment_affiliate_settings();
$key = $level >= 2 ? 'vip_2' : ($level >= 1 ? 'vip_1' : 'user');
return (float) ($settings['ratios'][$key] ?? 0);
}
public static function get_affiliate_settings(): array { return self::get_payment_affiliate_settings(); }
public static function get_settlement_settings(): array {
$s = self::get_payment_affiliate_settings();
@@ -436,18 +440,20 @@ class PaymentRuntime {
];
}
/** @return array{min_amount:float,service_charge_rate:int,daily_count:int,daily_limit:float,reset_time:string} */
/** @return array{min_amount:float,service_charge_rate:int,daily_count:int,daily_limit:float,reset_time:string,payout_channels:string[]} */
public static function get_withdrawal_settings(): array {
$s = Settings::get_all();
$affiliate = self::get_payment_affiliate_settings();
$channels = array_values(array_intersect(['alipay', 'wechat'], array_map('sanitize_key', (array) ($s['affiliate_payout_channels'] ?? ['alipay', 'wechat']))));
if (!$channels) { $channels = ['alipay', 'wechat']; }
return [
'min_amount' => (float) $affiliate['min_withdrawal'],
'service_charge_rate' => (int) round((float) $affiliate['withdrawal_fee']),
'min_amount' => max(0, min(999999, (float) ($s['affiliate_min_withdrawal'] ?? 50))),
'service_charge_rate' => (int) round(max(0, min(100, (float) ($s['affiliate_withdrawal_fee'] ?? 5)))),
'daily_count' => max(0, min(99999, (int) ($s['affiliate_daily_withdrawal_count'] ?? 3))),
'daily_limit' => max(0, min(999999, (float) ($s['affiliate_daily_withdrawal_limit'] ?? 500))),
'reset_time' => preg_match('/^(?:[01]\\d|2[0-3]):[0-5]\\d$/', (string) ($s['affiliate_withdrawal_reset_time'] ?? '00:00'))
? (string) $s['affiliate_withdrawal_reset_time']
: '00:00',
'payout_channels' => $channels,
];
}