From 450b9f44b90db6f969de0f140901c340838640f5 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 7 Aug 2026 09:15:42 +0800 Subject: [PATCH] feat: add creator income order tracking\n\nCo-Authored-By: Claude Fable 5 --- src/Admin/Native/PayIncome.php | 2 +- src/Content/PayOrders.php | 9 +- src/Core/DBMigrator.php | 2 +- src/Core/DBMigratorPart2.php | 20 ++- src/Payment/CreatorRevenue.php | 187 ++++---------------- src/Payment/Revenue.php | 292 +++++++++++++++++--------------- src/Settings/PaymentRuntime.php | 6 +- 7 files changed, 217 insertions(+), 301 deletions(-) diff --git a/src/Admin/Native/PayIncome.php b/src/Admin/Native/PayIncome.php index c4e2686d..e5e6bef8 100644 --- a/src/Admin/Native/PayIncome.php +++ b/src/Admin/Native/PayIncome.php @@ -88,7 +88,7 @@ class PayIncome { } foreach ($items as $row) { $id = (int) ($row['id'] ?? 0); - $user_id = (int) ($row['user_id'] ?? 0); + $user_id = (int) ($row['author_id'] ?? 0); $user = $user_id > 0 ? get_userdata($user_id) : null; $st = (string) ($row['status'] ?? ''); echo ''; diff --git a/src/Content/PayOrders.php b/src/Content/PayOrders.php index b0012039..2a8a49a0 100644 --- a/src/Content/PayOrders.php +++ b/src/Content/PayOrders.php @@ -55,11 +55,18 @@ trait PayOrders { // Consume coupon + write affiliate commission (cash content only; idempotent). $this->finalize_content_order_side_effects($order_number, $paid_order); if (class_exists('\AKSRT\Payment\OrderRepository')) { - \AKSRT\Payment\OrderRepository::get_instance()->update_status( + $completed = \AKSRT\Payment\OrderRepository::get_instance()->update_status( $order_number, \AKSRT\Payment\Order::STATUS_COMPLETED, ['note' => __('内容权益已发放。', 'aksrt')] ); + if ($completed && class_exists('\AKSRT\Payment\CreatorRevenue')) { + $completed_order = array_merge($paid_order, [ + 'order_number' => $order_number, + 'status' => \AKSRT\Payment\Order::STATUS_COMPLETED, + ]); + \AKSRT\Payment\CreatorRevenue::get_instance()->create_from_paid_order($completed_order); + } } } diff --git a/src/Core/DBMigrator.php b/src/Core/DBMigrator.php index 6226d0a8..c409687e 100644 --- a/src/Core/DBMigrator.php +++ b/src/Core/DBMigrator.php @@ -13,7 +13,7 @@ class DBMigrator { use DBMigratorIdentity; /** Current schema version; changing it re-runs dbDelta. */ - const SCHEMA_VERSION = '1.18.0'; + const SCHEMA_VERSION = '1.19.0'; const OPT_KEY = 'aksrt_db_schema_version'; diff --git a/src/Core/DBMigratorPart2.php b/src/Core/DBMigratorPart2.php index 949c8973..3cf19c52 100644 --- a/src/Core/DBMigratorPart2.php +++ b/src/Core/DBMigratorPart2.php @@ -187,19 +187,29 @@ trait DBMigratorPart2 { ) {$charset_collate};"; dbDelta($sql_order); - // ---------- 9. 收益台账 ---------- + // ---------- 9. 作者收益台账 ---------- $sql_revenue = "CREATE TABLE {$prefix}aksrt_revenue ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - user_id BIGINT UNSIGNED NOT NULL, + author_id BIGINT UNSIGNED NOT NULL DEFAULT 0, order_number VARCHAR(32) NOT NULL DEFAULT '', - amount DECIMAL(10,2) NOT NULL DEFAULT 0, - type VARCHAR(32) NOT NULL DEFAULT 'income', + post_id BIGINT UNSIGNED NOT NULL DEFAULT 0, + buyer_id BIGINT UNSIGNED NOT NULL DEFAULT 0, + currency VARCHAR(8) NOT NULL DEFAULT 'CNY', + original_amount DECIMAL(18,2) NOT NULL DEFAULT 0, + share_ratio DECIMAL(5,2) NOT NULL DEFAULT 0, + author_level TINYINT UNSIGNED NOT NULL DEFAULT 0, + amount DECIMAL(18,2) NOT NULL DEFAULT 0, + type VARCHAR(32) NOT NULL DEFAULT 'order', status VARCHAR(20) NOT NULL DEFAULT 'pending', created_at DATETIME NOT NULL, settled_at DATETIME DEFAULT NULL, PRIMARY KEY (id), - KEY idx_user (user_id), + UNIQUE KEY uniq_creator_order (author_id, order_number), + KEY idx_author (author_id), KEY idx_order (order_number), + KEY idx_post (post_id), + KEY idx_buyer (buyer_id), + KEY idx_currency (currency), KEY idx_status (status), KEY idx_created (created_at) ) {$charset_collate};"; diff --git a/src/Payment/CreatorRevenue.php b/src/Payment/CreatorRevenue.php index 47f718a4..c9d9f5fa 100644 --- a/src/Payment/CreatorRevenue.php +++ b/src/Payment/CreatorRevenue.php @@ -9,164 +9,35 @@ defined('ABSPATH') || exit; class CreatorRevenue { - private static ?CreatorRevenue $instance = null; + private static ?CreatorRevenue $instance = null; - public static function get_instance(): self { - if (null === self::$instance) { - self::$instance = new self(); - } - return self::$instance; - } - - public function init(): void { - add_action('aksrt_order_paid', [$this, 'handle_order_paid'], 20, 2); - } - - /** - * Whether creator revenue settlement is enabled. - */ - private function is_enabled(): bool { - if (!class_exists('AKSRT\\Payment\\Revenue') || !class_exists('\AKSRT\Settings\Settings')) { - return false; - } - $settings = \AKSRT\Settings\Settings::get_payment_revenue_settings(); - return !empty($settings['enabled']); - } - - /** - * Resolve share ratio (percentage 0-100) for a given product author. - */ - private function resolve_share_ratio(int $author_id): float { - $settings = \AKSRT\Settings\Settings::get_payment_revenue_settings(); - $ratio = (float) ($settings['share_ratio'] ?? 0); - - // VIP-tier author overrides, if configured (>0 wins). - if ($author_id > 0 && function_exists('aksrt_get_user_vip_level')) { - $level = (int) aksrt_get_user_vip_level($author_id); - if ($level === 1 && (float) ($settings['ratio_vip1'] ?? 0) > 0) { - $ratio = (float) $settings['ratio_vip1']; - } elseif ($level >= 2 && (float) ($settings['ratio_vip2'] ?? 0) > 0) { - $ratio = (float) $settings['ratio_vip2']; - } - } - - return max(0.0, min(100.0, $ratio)); - } - - /** - * Guard: has this order already been settled into revenue? - */ - private function already_settled(string $order_number): bool { - if ($order_number === '' || !class_exists('\AKSRT\Core\DBMigrator')) { - return false; - } - global $wpdb; - $table = \AKSRT\Core\DBMigrator::table('revenue'); - if ($table === '') { - return false; - } - $count = (int) $wpdb->get_var($wpdb->prepare( - "SELECT COUNT(*) FROM {$table} WHERE order_number = %s AND type = %s", - $order_number, - 'order' - )); - return $count > 0; - } - - /** - * aksrt_order_paid listener: credit creators their share. - * - * @param string $order_number Paid order number. - * @param array $order Order row (status=paid). - */ - public function handle_order_paid(string $order_number, array $order): void { - if (!$this->is_enabled() || $order_number === '') { - return; - } - if ($this->already_settled($order_number)) { - return; - } - - $buyer_id = (int) ($order['user_id'] ?? 0); - - $this->settle_non_shop_cash_order($order_number, $order, $buyer_id); - } - - /** - * Content / topic cash orders (no shop line items). - */ - private function settle_non_shop_cash_order(string $order_number, array $order, int $buyer_id): void { - $meta = $this->decode_note($order); - if (($meta['pay_modo'] ?? '') === 'points') { - return; - } - - $payment_method = sanitize_key((string) ($order['payment_method'] ?? '')); - $currency = sanitize_key((string) ($order['currency'] ?? '')); - if ($payment_method === 'points' || $currency === 'points') { - return; - } - - $base_amount = round((float) ($order['amount'] ?? $meta['payable'] ?? 0), 2); - if ($base_amount <= 0) { - return; - } - - $product_type = sanitize_key((string) ($order['product_type'] ?? '')); - $product_id = (int) ($order['product_id'] ?? 0); - if ($product_id <= 0) { - return; - } - - $post = get_post($product_id); - if (!($post instanceof \WP_Post)) { - return; - } - - $is_content = !empty($meta['content_pay']) - && $post->post_type === 'post'; - - // Resource without content_pay and not a known cash kind: skip. - if (!$is_content) { - return; - } - - $author_id = (int) $post->post_author; - if ($author_id <= 0 || $author_id === $buyer_id) { - return; - } - - $ratio = $this->resolve_share_ratio($author_id); - if ($ratio <= 0) { - return; - } - - $amount = round($base_amount * $ratio / 100, 2); - if ($amount <= 0) { - return; - } - - // Re-check after shop-less path; shop path already returned. - if ($this->already_settled($order_number)) { - return; - } - - Revenue::get_instance()->add($author_id, $amount, $order_number, 'order'); - } - - /** - * @param array $order - * @return array - */ - private function decode_note(array $order): array { - if (isset($order['meta_payload']) && is_array($order['meta_payload'])) { - return $order['meta_payload']; - } - $note = (string) ($order['note'] ?? ''); - if ($note === '') { - return []; - } - $decoded = json_decode($note, true); - return is_array($decoded) ? $decoded : []; + public static function get_instance(): self { + if (null === self::$instance) { + self::$instance = new self(); } + return self::$instance; } + + public function init(): void { + // Revenue is generated by the content fulfillment completion boundary. + } + + /** + * Create creator income after content entitlement has been delivered. + * + * @param array $order + */ + public function create_from_paid_order(array $order): int { + if (!$this->is_enabled() || !class_exists('AKSRT\\Payment\\Revenue')) { + return 0; + } + return Revenue::get_instance()->create_from_paid_order($order); + } + + private function is_enabled(): bool { + if (!class_exists('AKSRT\\Settings\\Settings')) { + return false; + } + return !empty(\AKSRT\Settings\Settings::get_payment_revenue_settings()['enabled']); + } +} diff --git a/src/Payment/Revenue.php b/src/Payment/Revenue.php index 49287f33..9f687aed 100644 --- a/src/Payment/Revenue.php +++ b/src/Payment/Revenue.php @@ -12,7 +12,9 @@ class Revenue { private static $instance = null; public static function get_instance(): self { - if (null === self::$instance) self::$instance = new self(); + if (null === self::$instance) { + self::$instance = new self(); + } return self::$instance; } @@ -21,27 +23,90 @@ class Revenue { } /** - * 记录收益 + * Create one pending creator-income record from a completed paid order. + * + * @param array $order */ - public function add(int $user_id, float $amount, string $order_number = '', string $type = 'income'): int { + public function create_from_paid_order(array $order): int { global $wpdb; - if ($user_id <= 0 || $amount <= 0) return 0; + $order_number = sanitize_text_field((string) ($order['order_number'] ?? '')); + if ($order_number === '' || (string) ($order['status'] ?? '') !== 'completed') { + return 0; + } - $wpdb->insert($this->table(), [ - 'user_id' => $user_id, - 'order_number' => sanitize_text_field($order_number), - 'amount' => $amount, - 'type' => sanitize_key($type), - 'status' => 'pending', - 'created_at' => current_time('mysql'), - ], ['%d', '%s', '%f', '%s', '%s', '%s']); + $meta = $this->decode_order_meta($order); + if (empty($meta['content_pay'])) { + return 0; + } - $id = (int) $wpdb->insert_id; + $post_id = (int) ($order['product_id'] ?? $meta['post_id'] ?? 0); + $buyer_id = (int) ($order['user_id'] ?? 0); + $post = $post_id > 0 ? get_post($post_id) : null; + if (!($post instanceof \WP_Post) || $post->post_type !== 'post') { + return 0; + } + + $author_id = (int) $post->post_author; + if ($author_id <= 0 || $author_id === $buyer_id) { + return 0; + } + + $currency = sanitize_key((string) ($order['currency'] ?? 'CNY')); + $is_points = $currency === 'points' || ($meta['pay_modo'] ?? '') === 'points'; + $currency = $is_points ? 'points' : 'CNY'; + $original_amount = round((float) ($order['amount'] ?? $meta['amount'] ?? $meta['payable'] ?? 0), 2); + if ($is_points) { + $original_amount = (float) ceil($original_amount); + } + if ($original_amount <= 0) { + return 0; + } + + $author_level = function_exists('aksrt_get_user_vip_level') + ? max(0, (int) aksrt_get_user_vip_level($author_id)) + : 0; + $ratio = $this->resolve_share_ratio($author_level); + if ($ratio <= 0) { + return 0; + } + $amount = $is_points + ? (float) max(1, (int) floor($original_amount * $ratio / 100)) + : round($original_amount * $ratio / 100, 2); + if ($amount <= 0) { + return 0; + } + + $inserted = $wpdb->insert($this->table(), [ + 'author_id' => $author_id, + 'order_number' => $order_number, + 'post_id' => $post_id, + 'buyer_id' => $buyer_id, + 'currency' => $currency, + 'original_amount' => $original_amount, + 'share_ratio' => $ratio, + 'author_level' => $author_level, + 'amount' => $amount, + 'type' => 'order', + 'status' => 'pending', + 'created_at' => current_time('mysql'), + ], ['%d', '%s', '%d', '%d', '%s', '%f', '%f', '%d', '%f', '%s', '%s', '%s']); + + if ($inserted === false) { + // Duplicate callbacks are a successful no-op by contract. + $existing = $wpdb->get_var($wpdb->prepare( + "SELECT id FROM {$this->table()} WHERE author_id = %d AND order_number = %s LIMIT 1", + $author_id, + $order_number + )); + return (int) $existing; + } + + $id = (int) $wpdb->insert_id; if ($id > 0) { - do_action('aksrt_creator_income_added', $user_id, $amount, [ + do_action('aksrt_creator_income_added', $author_id, $amount, [ 'order_number' => $order_number, - 'type' => $type, + 'currency' => $currency, 'revenue_id' => $id, ]); } @@ -49,159 +114,122 @@ class Revenue { } /** - * 结算收益 + * Legacy generic insertion retained as the native income page entry point. */ - public function settle(int $revenue_id): bool { + public function add(int $user_id, float $amount, string $order_number = '', string $type = 'income'): int { global $wpdb; - - if (!class_exists('\AKSRT\Credits\Balance')) { - return false; - } - - $wpdb->query('START TRANSACTION'); - try { - $revenue = $wpdb->get_row($wpdb->prepare( - "SELECT id, user_id, amount, order_number FROM {$this->table()} WHERE id = %d AND status = %s FOR UPDATE", - $revenue_id, - 'pending' - ), ARRAY_A); - if (!is_array($revenue)) { - $wpdb->query('ROLLBACK'); - return false; - } - - $credited = \AKSRT\Credits\Balance::add( - (int) $revenue['user_id'], - (float) $revenue['amount'], - 'creator_income', - ['type' => 'revenue', 'id' => $revenue_id], - sprintf(__('创作收益结算(订单 %s)', 'aksrt'), (string) $revenue['order_number']) - ); - if (is_wp_error($credited)) { - $wpdb->query('ROLLBACK'); - return false; - } - - $settled = $wpdb->update( - $this->table(), - ['status' => 'settled', 'settled_at' => current_time('mysql')], - ['id' => $revenue_id, 'status' => 'pending'], - ['%s', '%s'], - ['%d', '%s'] - ); - if ($settled !== 1) { - $wpdb->query('ROLLBACK'); - return false; - } - - $wpdb->query('COMMIT'); - return true; - } catch (\Throwable $e) { - $wpdb->query('ROLLBACK'); - return false; + if ($user_id <= 0 || $amount <= 0) { + return 0; } + $order_number = sanitize_text_field($order_number); + $wpdb->insert($this->table(), [ + 'author_id' => $user_id, + 'order_number' => $order_number, + 'amount' => round($amount, 2), + 'original_amount' => round($amount, 2), + 'currency' => 'CNY', + 'type' => sanitize_key($type), + 'status' => 'pending', + 'created_at' => current_time('mysql'), + ], ['%d', '%s', '%f', '%f', '%s', '%s', '%s', '%s']); + return (int) $wpdb->insert_id; } /** - * 获取用户收益列表 + * Settle one pending record through its matching atomic balance service. */ + public function settle(int $revenue_id): bool { + global $wpdb; + $revenue = $wpdb->get_row($wpdb->prepare( + "SELECT id, author_id, amount, currency, order_number FROM {$this->table()} WHERE id = %d AND status = %s", + $revenue_id, + 'pending' + ), ARRAY_A); + if (!is_array($revenue)) { + return false; + } + + if ((string) $revenue['currency'] === 'points' && class_exists('\\AKSRT\\Credits\\Credits')) { + $credited = \AKSRT\Credits\Credits::add((int) $revenue['author_id'], (int) $revenue['amount'], 'creator_income', ['type' => 'revenue', 'id' => $revenue_id]); + } elseif (class_exists('\\AKSRT\\Credits\\Balance')) { + $credited = \AKSRT\Credits\Balance::add((int) $revenue['author_id'], (float) $revenue['amount'], 'creator_income', ['type' => 'revenue', 'id' => $revenue_id]); + } else { + return false; + } + if (is_wp_error($credited)) { + return false; + } + + $updated = $wpdb->query($wpdb->prepare( + "UPDATE {$this->table()} SET status = %s, settled_at = %s WHERE id = %d AND status = %s", + 'settled', current_time('mysql'), $revenue_id, 'pending' + )); + return $updated === 1; + } + public function get_user_revenues(int $user_id, array $args = []): array { global $wpdb; - - $where = ['user_id = %d']; + $where = ['author_id = %d']; $params = [$user_id]; - $status = sanitize_key((string) ($args['status'] ?? '')); if ($status !== '') { $where[] = 'status = %s'; $params[] = $status; } - - $limit = max(1, min(100, (int) ($args['limit'] ?? 20))); + $limit = max(1, min(100, (int) ($args['limit'] ?? 20))); $offset = max(0, (int) ($args['offset'] ?? 0)); - - $sql = "SELECT * FROM {$this->table()} WHERE " . implode(' AND ', $where) . " ORDER BY created_at DESC LIMIT %d OFFSET %d"; $params[] = $limit; $params[] = $offset; - - return (array) $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A); + return (array) $wpdb->get_results($wpdb->prepare( + "SELECT * FROM {$this->table()} WHERE " . implode(' AND ', $where) . " ORDER BY created_at DESC LIMIT %d OFFSET %d", + $params + ), ARRAY_A); } - /** - * Admin list (all users) with optional status / user filter. - * - * @param array{status?:string,user_id?:int,limit?:int,offset?:int} $args - * @return array{items:array,total:int,page:int,max_pages:int} - */ public function get_list(array $args = []): array { global $wpdb; - - $limit = max(1, min(100, (int) ($args['limit'] ?? 20))); + $limit = max(1, min(100, (int) ($args['limit'] ?? 20))); $offset = max(0, (int) ($args['offset'] ?? 0)); - $status = sanitize_key((string) ($args['status'] ?? '')); - $user_id = (int) ($args['user_id'] ?? 0); - - $where = []; + $where = []; $params = []; - if ($status !== '') { - $where[] = 'status = %s'; + if (($status = sanitize_key((string) ($args['status'] ?? ''))) !== '') { + $where[] = 'status = %s'; $params[] = $status; } - if ($user_id > 0) { - $where[] = 'user_id = %d'; + if (($user_id = (int) ($args['user_id'] ?? 0)) > 0) { + $where[] = 'author_id = %d'; $params[] = $user_id; } $where_sql = $where ? 'WHERE ' . implode(' AND ', $where) : ''; - - $count_sql = "SELECT COUNT(*) FROM {$this->table()} {$where_sql}"; - $total = $params - ? (int) $wpdb->get_var($wpdb->prepare($count_sql, $params)) - : (int) $wpdb->get_var($count_sql); - - $sql = "SELECT * FROM {$this->table()} {$where_sql} ORDER BY created_at DESC LIMIT %d OFFSET %d"; - $params[] = $limit; - $params[] = $offset; - $items = $wpdb->get_results($wpdb->prepare($sql, $params), ARRAY_A); - - return [ - 'items' => $items ?: [], - 'total' => $total, - 'page' => (int) floor($offset / $limit) + 1, - 'max_pages' => (int) ceil(max(1, $total) / $limit), - ]; + $total = (int) ($params ? $wpdb->get_var($wpdb->prepare("SELECT COUNT(*) FROM {$this->table()} {$where_sql}", $params)) : $wpdb->get_var("SELECT COUNT(*) FROM {$this->table()}")); + $items_params = array_merge($params, [$limit, $offset]); + $items = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$this->table()} {$where_sql} ORDER BY created_at DESC LIMIT %d OFFSET %d", $items_params), ARRAY_A); + return ['items' => $items ?: [], 'total' => $total, 'page' => (int) floor($offset / $limit) + 1, 'max_pages' => (int) ceil(max(1, $total) / $limit)]; } - /** - * 获取收益统计 - */ public function get_stats(int $user_id = 0): array { global $wpdb; + $where = $user_id > 0 ? 'WHERE author_id = %d' : ''; + $params = $user_id > 0 ? [$user_id] : []; + $row = $params ? $wpdb->get_row($wpdb->prepare("SELECT COALESCE(SUM(amount),0) total, COALESCE(SUM(CASE WHEN status='pending' THEN amount ELSE 0 END),0) pending, COALESCE(SUM(CASE WHEN status='settled' THEN amount ELSE 0 END),0) settled, COUNT(*) count FROM {$this->table()} {$where}", $params), ARRAY_A) : $wpdb->get_row("SELECT COALESCE(SUM(amount),0) total, COALESCE(SUM(CASE WHEN status='pending' THEN amount ELSE 0 END),0) pending, COALESCE(SUM(CASE WHEN status='settled' THEN amount ELSE 0 END),0) settled, COUNT(*) count FROM {$this->table()}", ARRAY_A); + return ['total' => (float) ($row['total'] ?? 0), 'pending' => (float) ($row['pending'] ?? 0), 'settled' => (float) ($row['settled'] ?? 0), 'count' => (int) ($row['count'] ?? 0)]; + } - $where = ''; - $params = []; - if ($user_id > 0) { - $where = 'WHERE user_id = %d'; - $params[] = $user_id; + private function resolve_share_ratio(int $author_level): float { + $settings = \AKSRT\Settings\Settings::get_payment_revenue_settings(); + $ratio = $author_level >= 2 ? 80.0 : ($author_level === 1 ? 70.0 : 60.0); + $key = $author_level >= 2 ? 'ratio_vip2' : ($author_level === 1 ? 'ratio_vip1' : 'share_ratio'); + if (isset($settings[$key]) && is_numeric($settings[$key])) { + $ratio = (float) $settings[$key]; } + return max(0, min(100, $ratio)); + } - $sql = "SELECT - COALESCE(SUM(amount), 0) as total, - COALESCE(SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END), 0) as pending, - COALESCE(SUM(CASE WHEN status = 'settled' THEN amount ELSE 0 END), 0) as settled, - COUNT(*) as count - FROM {$this->table()} {$where}"; - - if (!empty($params)) { - $row = $wpdb->get_row($wpdb->prepare($sql, $params), ARRAY_A); - } else { - $row = $wpdb->get_row($sql, ARRAY_A); + private function decode_order_meta(array $order): array { + if (is_array($order['meta_payload'] ?? null)) { + return $order['meta_payload']; } - - return [ - 'total' => (float) ($row['total'] ?? 0), - 'pending' => (float) ($row['pending'] ?? 0), - 'settled' => (float) ($row['settled'] ?? 0), - 'count' => (int) ($row['count'] ?? 0), - ]; + $decoded = json_decode((string) ($order['note'] ?? ''), true); + return is_array($decoded) ? $decoded : []; } } diff --git a/src/Settings/PaymentRuntime.php b/src/Settings/PaymentRuntime.php index 04b8376d..981e338e 100644 --- a/src/Settings/PaymentRuntime.php +++ b/src/Settings/PaymentRuntime.php @@ -245,9 +245,9 @@ class PaymentRuntime { return [ 'enabled' => !empty($settings['pay_income_s']), - 'share_ratio' => max(0, min(100, (int) ($settings['payment_revenue_share_ratio'] ?? 70))), - 'ratio_vip1' => max(0, min(100, (int) ($settings['ratio_vip_1'] ?? 0))), - 'ratio_vip2' => max(0, min(100, (int) ($settings['ratio_vip_2'] ?? 0))), + 'share_ratio' => max(0, min(100, (int) ($settings['payment_revenue_share_ratio'] ?? 60))), + 'ratio_vip1' => max(0, min(100, (int) ($settings['ratio_vip_1'] ?? 70))), + 'ratio_vip2' => max(0, min(100, (int) ($settings['ratio_vip_2'] ?? 80))), 'settlement_threshold' => max(0, (int) ($settings['payment_revenue_settlement_threshold'] ?? 100)), 'cycle_days' => max(1, min(365, (int) ($settings['payment_revenue_cycle_days'] ?? 30))), 'points_rule' => sanitize_textarea_field((string) ($settings['payment_revenue_points_rule'] ?? '')),