feat(payment): 支付&付费菜单按参考主题重构并新增提现策略页
- 子菜单重组为知识付费/余额充值/VIP会员/创作分成/推广返佣/收款接口/提现策略 - 收款接口页 1:1 复刻 Zibll 双下拉结构,仅保留支付宝/微信/PayPal/Stripe/易支付 - 订单前缀/流水号前缀/提现单号前缀配置保留并移入收款接口页 - 知识付费/推广返佣按参考主题全字段复刻,余额充值并入充值卡配置 - 新增 tabbed/between_number/page_select 表单控件及后端 pages 搜索源 - 清理旧 SPA 组件、无消费者 REST 端点与废弃配置键(不写双路兼容) - 更新存量契约测试至新 schema 文件
This commit is contained in:
@@ -160,7 +160,7 @@ trait CreditsPaymentGroups {
|
||||
public static function get_settlement_settings(): array { return PaymentRuntime::get_settlement_settings(); }
|
||||
|
||||
/**
|
||||
* @return array{mode:string,test_enabled:bool,manual_review:bool,callback_secret:string,help_text:string}
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function get_payment_gateway_settings(): array {
|
||||
return PaymentRuntime::get_payment_gateway_settings();
|
||||
@@ -169,7 +169,7 @@ trait CreditsPaymentGroups {
|
||||
/**
|
||||
* 获取订单设置
|
||||
*
|
||||
* @return array{prefix:string,flow_prefix:string,withdrawal_prefix:string,expiry:int,show_user:bool,show_paycount:bool,no_logged_in:bool,cookie_day:int,no_logged_remind:bool,wait_float:bool,order_max_minutes:int,mark:string,box_position:string,show_ip:bool,show_device:bool,show_order_no:bool,show_create_time:bool,show_pay_time:bool,status_texts:array,service:array}
|
||||
* @return array{prefix:string,flow_prefix:string,withdrawal_prefix:string,expiry:int,show_user:bool,show_paycount:bool,no_logged_in:bool,cookie_day:int,no_logged_remind:bool,wait_float:bool,mark:string,box_position:string,show_ip:bool,show_device:bool,show_order_no:bool,show_create_time:bool,show_pay_time:bool,status_texts:array,service:array}
|
||||
*/
|
||||
public static function get_order_settings(): array {
|
||||
return PaymentRuntime::get_order_settings();
|
||||
|
||||
+173
-82
@@ -34,27 +34,29 @@ class PaymentRuntime {
|
||||
'catalog_note' => '',
|
||||
'manual_delivery_note' => __('付费或人工确认内容请在订单备注中记录交付方式。', 'aksrt'),
|
||||
'no_logged_in' => !isset($settings['pay_no_logged_in']) || !empty($settings['pay_no_logged_in']),
|
||||
'down_alone_page' => false,
|
||||
'free_logged_show' => !isset($settings['pay_free_logged_show']) || !empty($settings['pay_free_logged_show']),
|
||||
'down_alone_page' => !empty($settings['down_alone_page']),
|
||||
'display' => [
|
||||
'mark' => sanitize_text_field((string) ($settings['pay_mark'] ?? '付费')),
|
||||
'currency_symbol' => '¥',
|
||||
'currency_symbol' => sanitize_text_field((string) ($settings['pay_currency_mark'] ?? '¥')),
|
||||
'currency_unit' => sanitize_text_field((string) ($settings['pay_currency_unit'] ?? '元')),
|
||||
'show_user' => !isset($settings['pay_show_user']) || !empty($settings['pay_show_user']),
|
||||
'show_paycount' => !isset($settings['pay_show_paycount']) || !empty($settings['pay_show_paycount']),
|
||||
'box_position' => $box_position,
|
||||
],
|
||||
'download' => [
|
||||
'enabled' => true,
|
||||
'remote' => true,
|
||||
'verify_nonce' => true,
|
||||
'user_log' => 50,
|
||||
'post_log' => 50,
|
||||
'remote_size' => 2048,
|
||||
'remote_ext' => 'zip,rar,7z',
|
||||
'alone_page' => false,
|
||||
'remote' => !isset($settings['down_remote_to_local_s']) || !empty($settings['down_remote_to_local_s']),
|
||||
'verify_nonce' => !isset($settings['down_verify_nonce']) || !empty($settings['down_verify_nonce']),
|
||||
'user_log' => max(0, (int) ($settings['down_user_log'] ?? 50)),
|
||||
'post_log' => max(0, (int) ($settings['down_post_log'] ?? 50)),
|
||||
'remote_size' => max(0, (int) ($settings['down_remote_to_local_size'] ?? 2048)),
|
||||
'remote_ext' => sanitize_text_field((string) ($settings['down_remote_to_local_ext'] ?? 'zip,rar,7z')),
|
||||
'alone_page' => !empty($settings['down_alone_page']),
|
||||
],
|
||||
// 内容收银台优惠码:不再提供全局开关,默认关闭。
|
||||
'coupon_post_s' => false,
|
||||
'coupon_desc' => '',
|
||||
// 内容收银台优惠码:全局开关 + 默认说明。
|
||||
'coupon_post_s' => !empty($settings['coupon_post_s']),
|
||||
'coupon_desc' => sanitize_textarea_field((string) ($settings['coupon_desc'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -87,6 +89,7 @@ class PaymentRuntime {
|
||||
|
||||
return [
|
||||
'enabled' => !empty($settings['pay_balance_s']),
|
||||
'withdraw_enabled' => !empty($settings['pay_balance_withdraw_s']),
|
||||
'desc' => $desc,
|
||||
'packages_raw' => $packages,
|
||||
'package_count' => self::count_structured_lines($packages),
|
||||
@@ -279,28 +282,64 @@ class PaymentRuntime {
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiliate policy. Layout follows the reference theme: one rebate rule
|
||||
* set per user tier (pay_rebate_user_s / _1 / _2 + pay_rebate_ratio*).
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function get_payment_affiliate_settings(): array {
|
||||
$settings = Settings::get_all();
|
||||
$clamp_ratio = static fn($value): int => max(0, min(100, (int) $value));
|
||||
$types = (array) ($settings['affiliate_order_types'] ?? ['membership', 'content']);
|
||||
$types = array_values(array_intersect(['membership', 'content', 'credits'], array_map('sanitize_key', $types)));
|
||||
if (!$types) { $types = ['membership', 'content']; }
|
||||
|
||||
$resolve_types = static function (array $types): array {
|
||||
$types = array_values(array_intersect(['all', 'membership', 'content', 'credits'], array_map('sanitize_key', $types)));
|
||||
// 「全部订单」展开为全部可返佣订单类型。
|
||||
if (in_array('all', $types, true)) {
|
||||
return ['membership', 'content', 'credits'];
|
||||
}
|
||||
return $types;
|
||||
};
|
||||
|
||||
$types_by_level = [
|
||||
0 => $resolve_types((array) ($settings['pay_rebate_user_s'] ?? [])),
|
||||
1 => $resolve_types((array) ($settings['pay_rebate_user_s_1'] ?? ['all'])),
|
||||
2 => $resolve_types((array) ($settings['pay_rebate_user_s_2'] ?? ['all'])),
|
||||
];
|
||||
|
||||
$judgment = sanitize_key((string) ($settings['pay_rebate_judgment'] ?? 'all'));
|
||||
if (!in_array($judgment, ['all', 'link'], true)) {
|
||||
$judgment = 'all';
|
||||
}
|
||||
|
||||
return [
|
||||
'enabled' => !empty($settings['affiliate_enabled']),
|
||||
'enabled' => !empty($settings['pay_rebate_s']),
|
||||
'judgment' => $judgment,
|
||||
'show_users' => !isset($settings['pay_rebate_show_users']) || !empty($settings['pay_rebate_show_users']),
|
||||
'self_rebate' => !isset($settings['pay_rebate_self_s']) || !empty($settings['pay_rebate_self_s']),
|
||||
'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),
|
||||
'user' => $clamp_ratio($settings['pay_rebate_ratio'] ?? 5),
|
||||
'vip_1' => $clamp_ratio($settings['pay_rebate_ratio_vip_1'] ?? 10),
|
||||
'vip_2' => $clamp_ratio($settings['pay_rebate_ratio_vip_2'] ?? 20),
|
||||
],
|
||||
'order_types' => $types,
|
||||
'order_types' => $types_by_level[0],
|
||||
'order_types_by_level' => $types_by_level,
|
||||
'window_days' => (int) CreditsRuntime::get_referral_settings()['window_days'],
|
||||
'settlement_days' => max(0, min(90, (int) ($settings['affiliate_settlement_days'] ?? 7))),
|
||||
'promotion_text' => sanitize_textarea_field((string) ($settings['affiliate_promotion_text'] ?? '')),
|
||||
'promotion_text' => sanitize_textarea_field((string) ($settings['pay_rebate_text_details'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 某等级推广者可获返佣的订单类型;空数组代表该等级用户不参与推广返佣。
|
||||
*
|
||||
* @return array<int,string>
|
||||
*/
|
||||
public static function affiliate_order_types_for_level(int $level): array {
|
||||
$settings = self::get_payment_affiliate_settings();
|
||||
$key = $level >= 2 ? 2 : ($level >= 1 ? 1 : 0);
|
||||
return (array) ($settings['order_types_by_level'][$key] ?? []);
|
||||
}
|
||||
|
||||
public static function affiliate_enabled(): bool { return !empty(self::get_payment_affiliate_settings()['enabled']); }
|
||||
public static function affiliate_ratio_for_user(int $user_id): int {
|
||||
$level = function_exists('aksrt_get_user_vip_level') ? (int) aksrt_get_user_vip_level($user_id) : 0;
|
||||
@@ -318,26 +357,55 @@ class PaymentRuntime {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gateway channel selection. WeChat / Alipay each pick between the
|
||||
* official SDK and 易支付; PayPal / Stripe are standalone toggles.
|
||||
*
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function get_payment_gateway_settings(): array {
|
||||
$settings = Settings::get_all();
|
||||
$gateway_keys = ['wechat', 'alipay', 'paypal', 'stripe', 'balance', 'card_pass', 'epay'];
|
||||
$gateways = [];
|
||||
foreach ($gateway_keys as $gw) {
|
||||
$gateways[$gw] = !empty($settings["payment_{$gw}_enabled"]);
|
||||
|
||||
$wechat = sanitize_key((string) ($settings['pay_wechat_sdk_options'] ?? 'null'));
|
||||
if (!in_array($wechat, ['official_wechat', 'epay', 'null'], true)) {
|
||||
$wechat = 'null';
|
||||
}
|
||||
$alipay = sanitize_key((string) ($settings['pay_alipay_sdk_options'] ?? 'null'));
|
||||
if (!in_array($alipay, ['official_alipay', 'epay', 'null'], true)) {
|
||||
$alipay = 'null';
|
||||
}
|
||||
$paypal_enabled = !empty($settings['pay_paypal_sdk_s']);
|
||||
$stripe_enabled = !empty($settings['pay_stripe_sdk_s']);
|
||||
$balance_enabled = !empty($settings['pay_balance_s']);
|
||||
$card_pass_enabled = !empty($settings['pay_balance_pass_charge_s']);
|
||||
|
||||
return [
|
||||
'help_text' => __('以渠道开关与凭据是否齐全为准;异步回调由各网关验签。卡密为兑换入口,不进入收银台。', 'aksrt'),
|
||||
'gateways' => $gateways,
|
||||
'epay' => [
|
||||
'apiurl' => esc_url_raw((string) ($settings['epay_apiurl'] ?? '')),
|
||||
'pid' => sanitize_text_field((string) ($settings['epay_pid'] ?? '')),
|
||||
'key' => sanitize_text_field((string) ($settings['epay_key'] ?? '')),
|
||||
'channel_alipay' => !array_key_exists('epay_channel_alipay', $settings) || !empty($settings['epay_channel_alipay']),
|
||||
'channel_wxpay' => !array_key_exists('epay_channel_wxpay', $settings) || !empty($settings['epay_channel_wxpay']),
|
||||
'device' => sanitize_key((string) ($settings['epay_device'] ?? 'auto')),
|
||||
'help_text' => __('微信 / 支付宝可在官方与易支付之间选择;PayPal、Stripe 独立开关。异步回调由各网关验签。', 'aksrt'),
|
||||
'wechat_sdk_options' => $wechat,
|
||||
'alipay_sdk_options' => $alipay,
|
||||
'paypal_enabled' => $paypal_enabled,
|
||||
'stripe_enabled' => $stripe_enabled,
|
||||
'balance_enabled' => $balance_enabled,
|
||||
'card_pass_enabled' => $card_pass_enabled,
|
||||
'gateways' => [
|
||||
'wechat' => $wechat === 'official_wechat',
|
||||
'alipay' => $alipay === 'official_alipay',
|
||||
'paypal' => $paypal_enabled,
|
||||
'stripe' => $stripe_enabled,
|
||||
'balance' => $balance_enabled,
|
||||
'card_pass' => $card_pass_enabled,
|
||||
'epay' => $wechat === 'epay' || $alipay === 'epay',
|
||||
],
|
||||
'currency' => [
|
||||
'mark' => sanitize_text_field((string) ($settings['pay_currency_mark'] ?? '¥')),
|
||||
'unit' => sanitize_text_field((string) ($settings['pay_currency_unit'] ?? '元')),
|
||||
],
|
||||
'custom_title_s' => !empty($settings['pay_sdk_custom_title_s']),
|
||||
'custom_title' => sanitize_text_field((string) ($settings['pay_sdk_custom_title'] ?? '')),
|
||||
'epay' => [
|
||||
'apiurl' => esc_url_raw((string) ($settings['epay_apiurl'] ?? '')),
|
||||
'pid' => sanitize_text_field((string) ($settings['epay_pid'] ?? '')),
|
||||
'key' => sanitize_text_field((string) ($settings['epay_key'] ?? '')),
|
||||
'device' => sanitize_key((string) ($settings['epay_device'] ?? 'auto')),
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -352,7 +420,6 @@ class PaymentRuntime {
|
||||
|
||||
$expiry = max(5, min(1440, (int) ($s['payment_order_expiry'] ?? 30)));
|
||||
$cookie_day = max(1, min(31, (int) ($s['pay_cookie_day'] ?? 15)));
|
||||
$max_minutes = max(5, min(1440, (int) ($s['payment_order_max_minutes'] ?? 30)));
|
||||
|
||||
$box_position = sanitize_key((string) ($s['pay_box_position'] ?? 'top'));
|
||||
if (!in_array($box_position, ['box_top', 'top', 'bottom', 'box_bottom'], true)) {
|
||||
@@ -378,7 +445,6 @@ class PaymentRuntime {
|
||||
'cookie_day' => $cookie_day,
|
||||
'no_logged_remind' => sanitize_textarea_field((string) ($s['pay_no_logged_remind'] ?? '')),
|
||||
'wait_float' => !isset($s['pay_wait_float_s']) || !empty($s['pay_wait_float_s']),
|
||||
'order_max_minutes' => $max_minutes,
|
||||
'mark' => sanitize_text_field((string) ($s['pay_mark'] ?? '付费')),
|
||||
'box_position' => $box_position,
|
||||
'show_ip' => !empty($s['payment_show_ip']),
|
||||
@@ -387,13 +453,39 @@ class PaymentRuntime {
|
||||
'show_create_time' => !isset($s['payment_show_create_time']) || !empty($s['payment_show_create_time']),
|
||||
'show_pay_time' => !isset($s['payment_show_pay_time']) || !empty($s['payment_show_pay_time']),
|
||||
'status_texts' => $status_texts,
|
||||
'service' => [
|
||||
'text' => sanitize_textarea_field((string) ($s['pay_service'] ?? '')),
|
||||
'icon' => sanitize_text_field((string) ($s['pay_service_icon'] ?? '')),
|
||||
'value' => sanitize_text_field((string) ($s['pay_service_value'] ?? '')),
|
||||
'qq' => sanitize_text_field((string) ($s['pay_service_qq'] ?? '')),
|
||||
'wechat' => esc_url_raw((string) ($s['pay_service_wechat'] ?? '')),
|
||||
],
|
||||
'service' => self::format_service_items((array) ($s['pay_service'] ?? [])),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户服务:参考主题为 {value, icon} 数组;首项同时回填旧单值键,兼容既有消费方。
|
||||
*
|
||||
* @param array<int,mixed> $items
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private static function format_service_items(array $items): array {
|
||||
$clean = [];
|
||||
foreach ($items as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$value = sanitize_textarea_field((string) ($row['value'] ?? ''));
|
||||
if ($value === '') {
|
||||
continue;
|
||||
}
|
||||
$clean[] = [
|
||||
'value' => $value,
|
||||
'icon' => sanitize_text_field((string) ($row['icon'] ?? '')),
|
||||
];
|
||||
}
|
||||
$first = $clean[0] ?? [];
|
||||
return [
|
||||
'items' => $clean,
|
||||
'text' => (string) ($first['value'] ?? ''),
|
||||
'icon' => (string) ($first['icon'] ?? ''),
|
||||
'value' => (string) ($first['value'] ?? ''),
|
||||
'qq' => '',
|
||||
'wechat' => '',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -462,48 +554,43 @@ class PaymentRuntime {
|
||||
|
||||
/**
|
||||
* Whether a registered adapter is admin-enabled.
|
||||
* Maps gateway ids (wechatpay) to schema toggles (payment_wechat_enabled).
|
||||
* Gateway ids (wechatpay) map to the reference-theme channel selectors:
|
||||
* pay_wechat_sdk_options / pay_alipay_sdk_options / pay_paypal_sdk_s / pay_stripe_sdk_s.
|
||||
*/
|
||||
public static function is_gateway_enabled(string $gateway_id): bool {
|
||||
$settings = Settings::get_all();
|
||||
$gateway_id = sanitize_key($gateway_id);
|
||||
$map = [
|
||||
'alipay' => 'payment_alipay_enabled',
|
||||
'wechatpay' => 'payment_wechat_enabled',
|
||||
'paypal' => 'payment_paypal_enabled',
|
||||
'stripe' => 'payment_stripe_enabled',
|
||||
'balance' => 'payment_balance_enabled',
|
||||
'card_pass' => 'payment_card_pass_enabled',
|
||||
'epay' => 'payment_epay_enabled',
|
||||
'epay_alipay' => 'payment_epay_enabled',
|
||||
'epay_wxpay' => 'payment_epay_enabled',
|
||||
];
|
||||
$key = $map[$gateway_id] ?? '';
|
||||
if ($key === '') {
|
||||
return false;
|
||||
|
||||
$wechat = sanitize_key((string) ($settings['pay_wechat_sdk_options'] ?? 'null'));
|
||||
if (!in_array($wechat, ['official_wechat', 'epay', 'null'], true)) {
|
||||
$wechat = 'null';
|
||||
}
|
||||
if ($gateway_id === 'balance') {
|
||||
// Master: 余额充值 → 启用余额功能. Optional kill-switch: payment_balance_enabled=false.
|
||||
// Missing key (legacy sites) follows pay_balance_s so "open wallet" alone enables pay.
|
||||
if (empty($settings['pay_balance_s'])) {
|
||||
return false;
|
||||
}
|
||||
if (array_key_exists('payment_balance_enabled', $settings) && empty($settings['payment_balance_enabled'])) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
$alipay = sanitize_key((string) ($settings['pay_alipay_sdk_options'] ?? 'null'));
|
||||
if (!in_array($alipay, ['official_alipay', 'epay', 'null'], true)) {
|
||||
$alipay = 'null';
|
||||
}
|
||||
if (empty($settings[$key])) {
|
||||
return false;
|
||||
|
||||
switch ($gateway_id) {
|
||||
case 'alipay':
|
||||
return $alipay === 'official_alipay';
|
||||
case 'wechatpay':
|
||||
return $wechat === 'official_wechat';
|
||||
case 'paypal':
|
||||
return !empty($settings['pay_paypal_sdk_s']);
|
||||
case 'stripe':
|
||||
return !empty($settings['pay_stripe_sdk_s']);
|
||||
case 'balance':
|
||||
return !empty($settings['pay_balance_s']);
|
||||
case 'card_pass':
|
||||
return !empty($settings['pay_balance_pass_charge_s']);
|
||||
case 'epay':
|
||||
return $wechat === 'epay' || $alipay === 'epay';
|
||||
case 'epay_alipay':
|
||||
return $alipay === 'epay';
|
||||
case 'epay_wxpay':
|
||||
return $wechat === 'epay';
|
||||
}
|
||||
// Epay sub-channels require the matching channel switch (default on when key missing).
|
||||
if ($gateway_id === 'epay_alipay') {
|
||||
return !array_key_exists('epay_channel_alipay', $settings) || !empty($settings['epay_channel_alipay']);
|
||||
}
|
||||
if ($gateway_id === 'epay_wxpay') {
|
||||
return !array_key_exists('epay_channel_wxpay', $settings) || !empty($settings['epay_channel_wxpay']);
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function count_structured_lines(string $raw): int {
|
||||
@@ -530,6 +617,9 @@ class PaymentRuntime {
|
||||
}
|
||||
|
||||
/**
|
||||
* Format recharge product rows (reference-theme group shape:
|
||||
* price 充值金额 / pay_price 销售金额 / tag 小标签) into "支付|到账|标题" lines.
|
||||
*
|
||||
* @param array<int,mixed> $products
|
||||
*/
|
||||
private static function format_product_lines(array $products): string {
|
||||
@@ -538,10 +628,11 @@ class PaymentRuntime {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$price = $row['pay_price'] ?? $row['price'] ?? '';
|
||||
$value = $row['value'] ?? $row['balance'] ?? $price;
|
||||
$title = $row['title'] ?? $row['name'] ?? '';
|
||||
$lines[] = $price . '|' . $value . '|' . $title;
|
||||
$price = max(0, (float) ($row['price'] ?? 0));
|
||||
$pay_price = max(0, (float) ($row['pay_price'] ?? 0));
|
||||
$pay = $pay_price > 0 ? $pay_price : $price;
|
||||
$title = trim((string) ($row['tag'] ?? ''));
|
||||
$lines[] = $pay . '|' . $price . '|' . $title;
|
||||
}
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user