From c93b303453a2e76641ef94eefcfc8acd7adbccc2 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 11 Jul 2026 17:42:35 +0800 Subject: [PATCH] feat(payment): finish Zibll key contract and wire checkout gateways Drop dual-read payment runtime fallbacks, align PHP schema to Vue keys, read gateway credentials only from aksrt_settings with enable toggles, expose configured adapters at checkout, fix credits pay signatures, and apply order expiry settings. Wire orphan order/card-pass/withdrawal schema pages; delete unused Payment* wrappers. --- .../admin/src/constants/sections/payment.js | 33 +++++ .../src/pages/payment/PaymentAffiliate.vue | 16 --- .../src/pages/payment/PaymentRecharge.vue | 16 --- assets/admin/src/pages/payment/PaymentVip.vue | 16 --- .../src/schema/sections/payment/affiliate.js | 1 - .../src/schema/sections/payment/index.js | 1 + .../src/schema/sections/payment/recharge.js | 4 +- .../class-aksrt-admin-schema-payment.php | 55 ++++---- inc/classes/class-aksrt-payment-gateway.php | 46 ++++--- .../gateways/class-aksrt-alipay-gateway.php | 33 +++-- .../gateways/class-aksrt-paypal-gateway.php | 27 ++-- .../gateways/class-aksrt-stripe-gateway.php | 29 +++-- .../class-aksrt-wechatpay-gateway.php | 33 +++-- .../class-aksrt-settings-payment-runtime.php | 119 +++++++++++------- .../class-aksrt-shop-checkout-service.php | 22 +++- .../shop/class-aksrt-shop-product-service.php | 75 ++++++++--- 16 files changed, 298 insertions(+), 228 deletions(-) delete mode 100644 assets/admin/src/pages/payment/PaymentAffiliate.vue delete mode 100644 assets/admin/src/pages/payment/PaymentRecharge.vue delete mode 100644 assets/admin/src/pages/payment/PaymentVip.vue diff --git a/assets/admin/src/constants/sections/payment.js b/assets/admin/src/constants/sections/payment.js index 2c8a4de0..ba6299ba 100644 --- a/assets/admin/src/constants/sections/payment.js +++ b/assets/admin/src/constants/sections/payment.js @@ -49,6 +49,17 @@ export const paymentSection = { canonicalDomain: 'payment', section: 'vip', }), + schemaPage({ + key: 'payment-order', + name: 'PaymentOrder', + label: '订单设置', + route: '/payment/order', + status: 'available', + title: '订单设置', + desc: '订单前缀、有效期、展示字段和状态文案。', + canonicalDomain: 'payment', + section: 'order', + }), customPage({ key: 'payment-revenue', name: 'PaymentRevenue', @@ -84,6 +95,28 @@ export const paymentSection = { canonicalDomain: 'payment', section: 'charge-card', }), + schemaPage({ + key: 'payment-card-pass', + name: 'PaymentCardPass', + label: '卡密设置', + route: '/payment/card-pass', + status: 'available', + title: '卡密设置', + desc: '充值卡类型、卡号规则与兑换文案。', + canonicalDomain: 'payment', + section: 'card-pass', + }), + schemaPage({ + key: 'payment-withdrawal', + name: 'PaymentWithdrawal', + label: '提现策略', + route: '/payment/withdrawal', + status: 'available', + title: '提现策略', + desc: '最低提现、手续费与说明文案(提现工单在商城提现管理)。', + canonicalDomain: 'payment', + section: 'withdrawal', + }), customPage({ key: 'payment-rebate', name: 'PaymentRebate', diff --git a/assets/admin/src/pages/payment/PaymentAffiliate.vue b/assets/admin/src/pages/payment/PaymentAffiliate.vue deleted file mode 100644 index 3dd61e53..00000000 --- a/assets/admin/src/pages/payment/PaymentAffiliate.vue +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/assets/admin/src/pages/payment/PaymentRecharge.vue b/assets/admin/src/pages/payment/PaymentRecharge.vue deleted file mode 100644 index f9c3eaa0..00000000 --- a/assets/admin/src/pages/payment/PaymentRecharge.vue +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/assets/admin/src/pages/payment/PaymentVip.vue b/assets/admin/src/pages/payment/PaymentVip.vue deleted file mode 100644 index 9f0f09a6..00000000 --- a/assets/admin/src/pages/payment/PaymentVip.vue +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/assets/admin/src/schema/sections/payment/affiliate.js b/assets/admin/src/schema/sections/payment/affiliate.js index dcf3e9c9..7173752a 100644 --- a/assets/admin/src/schema/sections/payment/affiliate.js +++ b/assets/admin/src/schema/sections/payment/affiliate.js @@ -21,7 +21,6 @@ export const affiliateSchema = { pay_rebate_text_details_title: '', pay_rebate_text_details: '', pay_rebate_text_discount: '', - payment_affiliate_note: '返佣链路复用邀请体系,订单归因和结算自动衔接当前策略字段。', }, groups: [ { diff --git a/assets/admin/src/schema/sections/payment/index.js b/assets/admin/src/schema/sections/payment/index.js index 16741d18..c8aa0eaf 100644 --- a/assets/admin/src/schema/sections/payment/index.js +++ b/assets/admin/src/schema/sections/payment/index.js @@ -20,6 +20,7 @@ export const paymentSchemas = { affiliate: affiliateSchema, gateway: gatewaySchema, order: orderSchema, + 'card-pass': cardPassSchema, cardPass: cardPassSchema, withdrawal: withdrawalSchema, } diff --git a/assets/admin/src/schema/sections/payment/recharge.js b/assets/admin/src/schema/sections/payment/recharge.js index 3d4a8d66..46274cd6 100644 --- a/assets/admin/src/schema/sections/payment/recharge.js +++ b/assets/admin/src/schema/sections/payment/recharge.js @@ -2,7 +2,7 @@ export const rechargeSchema = { key: 'payment.recharge', title: '余额充值', icon: 'fas fa-charging-station', - description: '配置测试充值、手动确认和充值套餐结构。', + description: '配置余额充值套餐、转账与积分兑换。', defaults: { pay_balance_s: true, pay_balance_desc: '', @@ -31,7 +31,6 @@ export const rechargeSchema = { icon: 'fas fa-wallet', fields: [ { key: 'pay_balance_s', label: '启用余额功能', type: 'switch', default: true }, - { key: 'payment_recharge_mode', label: '充值模式', type: 'radio', default: 'manual_review', options: [{ label: '人工确认', value: 'manual_review' }, { label: '测试网关', value: 'test_gateway' }], dependsOn: { key: 'pay_balance_s', value: true } }, { key: 'pay_balance_desc', label: '充值说明', type: 'textarea', default: '', dependsOn: { key: 'pay_balance_s', value: true } }, { key: 'pay_auto_to_balance', label: '自动到账到余额', type: 'switch', default: true, dependsOn: { key: 'pay_balance_s', value: true } }, ], @@ -42,7 +41,6 @@ export const rechargeSchema = { icon: 'fas fa-box-open', fields: [ { key: 'pay_balance_product', label: '套餐列表', type: 'textarea', default: '30|30|入门包\n98|108|推荐包', help: '每行一个套餐:支付金额|到账金额|标题', dependsOn: { key: 'pay_balance_s', value: true } }, - { key: 'payment_recharge_help', label: '帮助文案', type: 'textarea', default: '当前支持人工确认与测试联调;接入真实网关时复用同一组字段。', dependsOn: { key: 'pay_balance_s', value: true } }, ], }, { diff --git a/inc/classes/admin/schema/class-aksrt-admin-schema-payment.php b/inc/classes/admin/schema/class-aksrt-admin-schema-payment.php index 505b6bfb..9433af92 100644 --- a/inc/classes/admin/schema/class-aksrt-admin-schema-payment.php +++ b/inc/classes/admin/schema/class-aksrt-admin-schema-payment.php @@ -57,23 +57,18 @@ class AKSRT_Admin_Schema_Payment { 'option_key' => 'aksrt_settings', 'groups' => [ ['key' => 'basic', 'title' => '充值开关', 'fields' => [ - ['key' => 'pay_balance_s', 'label' => '启用余额充值', 'type' => 'switch', 'default' => true], - ['key' => 'payment_recharge_mode', 'label' => '充值模式', 'type' => 'select', 'default' => 'manual_review', 'dependsOn' => ['key' => 'pay_balance_s', 'value' => true], 'options' => [ - ['label' => '人工审核', 'value' => 'manual_review'], - ['label' => '自动到账', 'value' => 'auto'], - ]], + ['key' => 'pay_balance_s', 'label' => '启用余额功能', 'type' => 'switch', 'default' => true], ['key' => 'pay_balance_desc', 'label' => '充值说明', 'type' => 'textarea', 'default' => '', 'dependsOn' => ['key' => 'pay_balance_s', 'value' => true]], ['key' => 'pay_auto_to_balance', 'label' => '自动到账到余额', 'type' => 'switch', 'default' => true, 'dependsOn' => ['key' => 'pay_balance_s', 'value' => true]], ]], ['key' => 'packages', 'title' => '充值套餐', 'fields' => [ ['key' => 'pay_balance_product', 'label' => '套餐列表', 'type' => 'textarea', 'default' => "30|30|入门包\n98|108|推荐包", 'dependsOn' => ['key' => 'pay_balance_s', 'value' => true]], - ['key' => 'payment_recharge_help', 'label' => '帮助文案', 'type' => 'textarea', 'default' => '', 'dependsOn' => ['key' => 'pay_balance_s', 'value' => true]], ]], ['key' => 'product', 'title' => '充值产品', 'fields' => [ - ['key' => 'payment_recharge_product', 'label' => '充值产品', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'pay_balance_s', 'value' => true]], - ['key' => 'payment_recharge_price', 'label' => '充值价格', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 99999, 'dependsOn' => ['key' => 'pay_balance_s', 'value' => true]], - ['key' => 'payment_recharge_tag', 'label' => '价格标签', 'type' => 'input', 'default' => '元', 'dependsOn' => ['key' => 'pay_balance_s', 'value' => true]], - ['key' => 'payment_recharge_tag_class', 'label' => '标签样式', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'pay_balance_s', 'value' => true]], + ['key' => 'pay_balance_product_name', 'label' => '充值产品', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'pay_balance_s', 'value' => true]], + ['key' => 'pay_balance_price', 'label' => '充值价格', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 99999, 'dependsOn' => ['key' => 'pay_balance_s', 'value' => true]], + ['key' => 'pay_balance_tag', 'label' => '价格标签', 'type' => 'input', 'default' => '元', 'dependsOn' => ['key' => 'pay_balance_s', 'value' => true]], + ['key' => 'pay_balance_tag_class', 'label' => '标签样式', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'pay_balance_s', 'value' => true]], ['key' => 'pay_balance_product_custom_s', 'label' => '自定义金额', 'type' => 'switch', 'default' => true, 'dependsOn' => ['key' => 'pay_balance_s', 'value' => true]], ['key' => 'pay_balance_product_custom_limit', 'label' => '自定义金额限制', 'type' => 'number', 'default' => 9999, 'min' => 1, 'max' => 99999, 'dependsOn' => ['key' => 'pay_balance_s', 'value' => true]], ]], @@ -88,9 +83,9 @@ class AKSRT_Admin_Schema_Payment { ['key' => 'pay_balance_pass_charge_only_password', 'label' => '仅密码免扣', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'pay_balance_pass_charge_s', 'value' => true]], ]], ['key' => 'exchange', 'title' => '积分兑换', 'fields' => [ - ['key' => 'payment_recharge_exchange_s', 'label' => '启用积分兑换', 'type' => 'switch', 'default' => false], - ['key' => 'payment_recharge_exchange_rate', 'label' => '兑换比例', 'type' => 'number', 'default' => 100, 'min' => 1, 'max' => 10000, 'dependsOn' => ['key' => 'payment_recharge_exchange_s', 'value' => true]], - ['key' => 'payment_recharge_exchange_desc', 'label' => '兑换说明', 'type' => 'textarea', 'default' => '', 'dependsOn' => ['key' => 'payment_recharge_exchange_s', 'value' => true]], + ['key' => 'pay_balance_exchange_s', 'label' => '启用积分兑换', 'type' => 'switch', 'default' => false], + ['key' => 'pay_balance_exchange_rate', 'label' => '兑换比例', 'type' => 'number', 'default' => 100, 'min' => 1, 'max' => 10000, 'dependsOn' => ['key' => 'pay_balance_exchange_s', 'value' => true]], + ['key' => 'pay_balance_exchange_desc', 'label' => '兑换说明', 'type' => 'textarea', 'default' => '', 'dependsOn' => ['key' => 'pay_balance_exchange_s', 'value' => true]], ]], ], ], @@ -100,7 +95,7 @@ class AKSRT_Admin_Schema_Payment { 'version' => '1.0.0', 'option_key' => 'aksrt_settings', 'groups' => [ - ['key' => 'basic', 'title' => '会员基础', 'fields' => [ + ['key' => 'basic', 'title' => '会员等级', 'fields' => [ ['key' => 'pay_user_vip_1_s', 'label' => '启用一级会员', 'type' => 'switch', 'default' => true], ['key' => 'pay_user_vip_1_name', 'label' => '一级会员名称', 'type' => 'input', 'default' => '一级会员', 'dependsOn' => ['key' => 'pay_user_vip_1_s', 'value' => true]], ['key' => 'pay_user_vip_2_s', 'label' => '启用二级会员', 'type' => 'switch', 'default' => true], @@ -109,14 +104,16 @@ class AKSRT_Admin_Schema_Payment { ]], ['key' => 'packages', 'title' => '会员套餐', 'fields' => [ ['key' => 'payment_vip_packages', 'label' => '套餐列表', 'type' => 'textarea', 'default' => "30|30|月卡\n365|365|年卡"], - ['key' => 'payment_vip_help', 'label' => '权益说明', 'type' => 'textarea', 'default' => ''], ]], - ['key' => 'benefit', 'title' => '会员权益', 'fields' => [ - ['key' => 'pay_download_limit', 'label' => '下载限制', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 9999], - ['key' => 'pay_download_speed', 'label' => '下载速度(KB/s)', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 99999], - ['key' => 'payment_vip_discount', 'label' => '折扣(%)', 'type' => 'number', 'default' => 100, 'min' => 1, 'max' => 100], - ['key' => 'pay_vip_pass_charge_s', 'label' => '积分充值免扣', 'type' => 'switch', 'default' => false], - ['key' => 'pay_vip_pass_charge_desc', 'label' => '积分充值说明', 'type' => 'textarea', 'default' => ''], + ['key' => 'benefit', 'title' => '下载权益', 'fields' => [ + ['key' => 'pay_download_limit', 'label' => '普通用户下载限制', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 9999], + ['key' => 'pay_download_speed', 'label' => '普通用户下载速度(KB/s)', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 99999], + ['key' => 'pay_download_limit_vip_1', 'label' => '一级会员下载限制', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 9999, 'dependsOn' => ['key' => 'pay_user_vip_1_s', 'value' => true]], + ['key' => 'pay_download_speed_vip_1', 'label' => '一级会员下载速度(KB/s)', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 99999, 'dependsOn' => ['key' => 'pay_user_vip_1_s', 'value' => true]], + ['key' => 'pay_download_limit_vip_2', 'label' => '二级会员下载限制', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 9999, 'dependsOn' => ['key' => 'pay_user_vip_2_s', 'value' => true]], + ['key' => 'pay_download_speed_vip_2', 'label' => '二级会员下载速度(KB/s)', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 99999, 'dependsOn' => ['key' => 'pay_user_vip_2_s', 'value' => true]], + ['key' => 'pay_vip_pass_charge_s', 'label' => '会员卡密兑换', 'type' => 'switch', 'default' => false], + ['key' => 'pay_vip_pass_charge_desc', 'label' => '卡密兑换说明', 'type' => 'textarea', 'default' => '', 'dependsOn' => ['key' => 'pay_vip_pass_charge_s', 'value' => true]], ]], ['key' => 'renew', 'title' => '续费设置', 'fields' => [ ['key' => 'vip_renew', 'label' => '允许续费', 'type' => 'switch', 'default' => true], @@ -134,14 +131,6 @@ class AKSRT_Admin_Schema_Payment { ['key' => 'vip_upgrade_desc', 'label' => '升级说明', 'type' => 'textarea', 'default' => '', 'dependsOn' => ['key' => 'vip_upgrade', 'value' => true]], ['key' => 'vip_upgrade_product', 'label' => '升级产品', 'type' => 'input', 'default' => '', 'dependsOn' => ['key' => 'vip_upgrade', 'value' => true]], ]], - ['key' => 'price', 'title' => '价格设置', 'fields' => [ - ['key' => 'payment_vip_month_price', 'label' => '月卡价格', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 99999], - ['key' => 'payment_vip_year_price', 'label' => '年卡价格', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 99999], - ['key' => 'payment_vip_permanent_price', 'label' => '永久价格', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 99999], - ['key' => 'payment_vip_unit_price', 'label' => '单位价格', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 99999], - ['key' => 'payment_vip_unit_tag', 'label' => '单位标签', 'type' => 'input', 'default' => '元'], - ['key' => 'payment_vip_jump_price', 'label' => '跳转价格', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 99999], - ]], ], ], 'revenue' => [ @@ -314,10 +303,10 @@ class AKSRT_Admin_Schema_Payment { ]], ['key' => 'service', 'title' => '服务设置', 'fields' => [ ['key' => 'pay_service', 'label' => '客服设置', 'type' => 'textarea', 'default' => ''], - ['key' => 'payment_service_icon', 'label' => '客服图标', 'type' => 'icon', 'default' => ''], - ['key' => 'payment_service_value', 'label' => '客服内容', 'type' => 'input', 'default' => ''], - ['key' => 'payment_service_qq', 'label' => 'QQ客服', 'type' => 'input', 'default' => ''], - ['key' => 'payment_service_wechat', 'label' => '微信客服', 'type' => 'media', 'default' => ''], + ['key' => 'pay_service_icon', 'label' => '客服图标', 'type' => 'icon', 'default' => ''], + ['key' => 'pay_service_value', 'label' => '客服内容', 'type' => 'input', 'default' => ''], + ['key' => 'pay_service_qq', 'label' => 'QQ客服', 'type' => 'input', 'default' => ''], + ['key' => 'pay_service_wechat', 'label' => '微信客服', 'type' => 'media', 'default' => ''], ]], ], ], diff --git a/inc/classes/class-aksrt-payment-gateway.php b/inc/classes/class-aksrt-payment-gateway.php index e254c179..2eabb62a 100644 --- a/inc/classes/class-aksrt-payment-gateway.php +++ b/inc/classes/class-aksrt-payment-gateway.php @@ -351,7 +351,13 @@ class AKSRT_Balance_Gateway implements AKSRT_Payment_Gateway_Interface { } public function is_configured(): bool { - return class_exists('AKSRT_Credits'); + if (!class_exists('AKSRT_Credits')) { + return false; + } + if (class_exists('AKSRT_Settings_Payment_Runtime')) { + return AKSRT_Settings_Payment_Runtime::is_gateway_enabled('balance'); + } + return true; } public function create_payment(array $order): array { @@ -362,6 +368,7 @@ class AKSRT_Balance_Gateway implements AKSRT_Payment_Gateway_Interface { $user_id = (int) ($order['user_id'] ?? 0); $amount = (float) ($order['amount'] ?? 0); $credits_cost = (int) ceil($amount * 100); // 1 元 = 100 积分 + $order_number = (string) ($order['order_number'] ?? ''); $balance = AKSRT_Credits::balance($user_id); @@ -369,18 +376,23 @@ class AKSRT_Balance_Gateway implements AKSRT_Payment_Gateway_Interface { return ['success' => false, 'message' => __('余额不足', 'aksrt')]; } - // Deduct credits - $result = AKSRT_Credits::deduct($user_id, $credits_cost, 'order_payment', sprintf(__('订单 %s 支付', 'aksrt'), $order['order_number'] ?? '')); + $result = AKSRT_Credits::deduct( + $user_id, + $credits_cost, + 'order_payment', + ['order' => $order_number], + sprintf(__('订单 %s 支付', 'aksrt'), $order_number) + ); - if ($result) { - return [ - 'success' => true, - 'transaction_id' => 'BAL_' . ($order['order_number'] ?? time()), - 'message' => __('余额支付成功', 'aksrt'), - ]; + if (is_wp_error($result)) { + return ['success' => false, 'message' => $result->get_error_message() ?: __('扣款失败', 'aksrt')]; } - return ['success' => false, 'message' => __('扣款失败', 'aksrt')]; + return [ + 'success' => true, + 'transaction_id' => 'BAL_' . ($order_number !== '' ? $order_number : (string) time()), + 'message' => __('余额支付成功', 'aksrt'), + ]; } public function verify_callback(WP_REST_Request $request): array { @@ -418,12 +430,18 @@ class AKSRT_Balance_Gateway implements AKSRT_Payment_Gateway_Interface { $user_id = (int) $order['user_id']; $credits_refund = (int) ceil($amount * 100); - $result = AKSRT_Credits::add($user_id, $credits_refund, 'order_refund', sprintf(__('订单 %s 退款', 'aksrt'), $order_number)); + $result = AKSRT_Credits::add( + $user_id, + $credits_refund, + 'order_refund', + ['order' => $order_number], + sprintf(__('订单 %s 退款', 'aksrt'), $order_number) + ); - if ($result) { - return ['success' => true, 'refund_id' => 'BAL_REF_' . $order_number, 'message' => __('退款成功', 'aksrt')]; + if (is_wp_error($result)) { + return ['success' => false, 'message' => $result->get_error_message() ?: __('退款失败', 'aksrt')]; } - return ['success' => false, 'message' => __('退款失败', 'aksrt')]; + return ['success' => true, 'refund_id' => 'BAL_REF_' . $order_number, 'message' => __('退款成功', 'aksrt')]; } } diff --git a/inc/classes/gateways/class-aksrt-alipay-gateway.php b/inc/classes/gateways/class-aksrt-alipay-gateway.php index 8d390d44..0098e278 100644 --- a/inc/classes/gateways/class-aksrt-alipay-gateway.php +++ b/inc/classes/gateways/class-aksrt-alipay-gateway.php @@ -20,6 +20,10 @@ class AKSRT_Alipay_Gateway implements AKSRT_Payment_Gateway_Interface { } public function is_configured(): bool { + if (class_exists('AKSRT_Settings_Payment_Runtime') + && !AKSRT_Settings_Payment_Runtime::is_gateway_enabled('alipay')) { + return false; + } $config = $this->get_config(); return !empty($config['app_id']) && !empty($config['private_key']) && !empty($config['alipay_public_key']); } @@ -165,24 +169,19 @@ class AKSRT_Alipay_Gateway implements AKSRT_Payment_Gateway_Interface { } private function get_config(): array { - // 优先从统一设置读取(schema 保存的 key) - if (class_exists('AKSRT_Settings')) { - $all = AKSRT_Settings::get_all(); - if (!empty($all['alipay_appid'])) { - return [ - 'app_id' => $all['alipay_appid'] ?? '', - 'private_key' => $all['alipay_privatekey'] ?? '', - 'alipay_public_key' => $all['alipay_publickey'] ?? '', - 'return_url' => $all['alipay_return_url'] ?? '', - 'notify_url' => $all['alipay_notify_url'] ?? '', - 'payment_type' => $all['alipay_payment_type'] ?? 'page', - 'sign_type' => $all['alipay_sign_type'] ?? 'RSA2', - ]; - } + if (!class_exists('AKSRT_Settings')) { + return []; } - // 回退到独立 option - $settings = get_option('aksrt_payment_alipay', []); - return is_array($settings) ? $settings : []; + $all = AKSRT_Settings::get_all(); + return [ + 'app_id' => (string) ($all['alipay_appid'] ?? ''), + 'private_key' => (string) ($all['alipay_privatekey'] ?? ''), + 'alipay_public_key' => (string) ($all['alipay_publickey'] ?? ''), + 'return_url' => (string) ($all['alipay_return_url'] ?? ''), + 'notify_url' => (string) ($all['alipay_notify_url'] ?? ''), + 'payment_type' => (string) ($all['alipay_payment_type'] ?? 'page'), + 'sign_type' => (string) ($all['alipay_sign_type'] ?? 'RSA2'), + ]; } private function create_sdk(array $config): \Yurun\PaySDK\AlipayApp\SDK { diff --git a/inc/classes/gateways/class-aksrt-paypal-gateway.php b/inc/classes/gateways/class-aksrt-paypal-gateway.php index 52fa6178..4aad5c7d 100644 --- a/inc/classes/gateways/class-aksrt-paypal-gateway.php +++ b/inc/classes/gateways/class-aksrt-paypal-gateway.php @@ -20,6 +20,10 @@ class AKSRT_PayPal_Gateway implements AKSRT_Payment_Gateway_Interface { } public function is_configured(): bool { + if (class_exists('AKSRT_Settings_Payment_Runtime') + && !AKSRT_Settings_Payment_Runtime::is_gateway_enabled('paypal')) { + return false; + } $config = $this->get_config(); return !empty($config['client_id']) && !empty($config['client_secret']); } @@ -128,21 +132,16 @@ class AKSRT_PayPal_Gateway implements AKSRT_Payment_Gateway_Interface { } private function get_config(): array { - // 优先从统一设置读取(schema 保存的 key) - if (class_exists('AKSRT_Settings')) { - $all = AKSRT_Settings::get_all(); - if (!empty($all['paypal_client_id'])) { - return [ - 'client_id' => $all['paypal_client_id'] ?? '', - 'client_secret' => $all['paypal_client_secret'] ?? '', - 'currency' => $all['paypal_currency'] ?? 'USD', - 'mode' => !empty($all['paypal_sandbox']) ? 'sandbox' : 'live', - ]; - } + if (!class_exists('AKSRT_Settings')) { + return []; } - // 回退到独立 option - $settings = get_option('aksrt_payment_paypal', []); - return is_array($settings) ? $settings : []; + $all = AKSRT_Settings::get_all(); + return [ + 'client_id' => (string) ($all['paypal_client_id'] ?? ''), + 'client_secret' => (string) ($all['paypal_client_secret'] ?? ''), + 'currency' => (string) ($all['paypal_currency'] ?? 'USD'), + 'mode' => !empty($all['paypal_sandbox']) ? 'sandbox' : 'live', + ]; } /** diff --git a/inc/classes/gateways/class-aksrt-stripe-gateway.php b/inc/classes/gateways/class-aksrt-stripe-gateway.php index ba47d8a2..780699ec 100644 --- a/inc/classes/gateways/class-aksrt-stripe-gateway.php +++ b/inc/classes/gateways/class-aksrt-stripe-gateway.php @@ -20,6 +20,10 @@ class AKSRT_Stripe_Gateway implements AKSRT_Payment_Gateway_Interface { } public function is_configured(): bool { + if (class_exists('AKSRT_Settings_Payment_Runtime') + && !AKSRT_Settings_Payment_Runtime::is_gateway_enabled('stripe')) { + return false; + } $config = $this->get_config(); return !empty($config['secret_key']) && !empty($config['publishable_key']); } @@ -233,22 +237,17 @@ class AKSRT_Stripe_Gateway implements AKSRT_Payment_Gateway_Interface { } private function get_config(): array { - // 优先从统一设置读取(schema 保存的 key) - if (class_exists('AKSRT_Settings')) { - $all = AKSRT_Settings::get_all(); - if (!empty($all['stripe_secret_key'])) { - return [ - 'secret_key' => $all['stripe_secret_key'] ?? '', - 'publishable_key' => $all['stripe_publishable_key'] ?? '', - 'webhook_secret' => $all['stripe_webhook_secret'] ?? '', - 'currency' => $all['stripe_currency'] ?? 'usd', - 'mode' => !empty($all['stripe_test_mode']) ? 'test' : 'live', - ]; - } + if (!class_exists('AKSRT_Settings')) { + return []; } - // 回退到独立 option - $settings = get_option('aksrt_payment_stripe', []); - return is_array($settings) ? $settings : []; + $all = AKSRT_Settings::get_all(); + return [ + 'secret_key' => (string) ($all['stripe_secret_key'] ?? ''), + 'publishable_key' => (string) ($all['stripe_publishable_key'] ?? ''), + 'webhook_secret' => (string) ($all['stripe_webhook_secret'] ?? ''), + 'currency' => (string) ($all['stripe_currency'] ?? 'usd'), + 'mode' => !empty($all['stripe_test_mode']) ? 'test' : 'live', + ]; } private function create_client(array $config): void { diff --git a/inc/classes/gateways/class-aksrt-wechatpay-gateway.php b/inc/classes/gateways/class-aksrt-wechatpay-gateway.php index 70cdc33c..bf6714dc 100644 --- a/inc/classes/gateways/class-aksrt-wechatpay-gateway.php +++ b/inc/classes/gateways/class-aksrt-wechatpay-gateway.php @@ -20,6 +20,10 @@ class AKSRT_WechatPay_Gateway implements AKSRT_Payment_Gateway_Interface { } public function is_configured(): bool { + if (class_exists('AKSRT_Settings_Payment_Runtime') + && !AKSRT_Settings_Payment_Runtime::is_gateway_enabled('wechatpay')) { + return false; + } $config = $this->get_config(); return !empty($config['mch_id']) && !empty($config['key']); } @@ -206,24 +210,19 @@ class AKSRT_WechatPay_Gateway implements AKSRT_Payment_Gateway_Interface { } private function get_config(): array { - // 优先从统一设置读取(schema 保存的 key) - if (class_exists('AKSRT_Settings')) { - $all = AKSRT_Settings::get_all(); - if (!empty($all['wechat_merchantid'])) { - return [ - 'mch_id' => $all['wechat_merchantid'] ?? '', - 'key' => $all['wechat_jsapi'] ?? '', - 'appid' => $all['wechat_appid'] ?? '', - 'appsecret' => $all['wechat_appsecret'] ?? '', - 'cert_path' => $all['wechat_cert_path'] ?? '', - 'key_path' => $all['wechat_key_path'] ?? '', - 'payment_type' => $all['wechat_payment_type'] ?? 'native', - ]; - } + if (!class_exists('AKSRT_Settings')) { + return []; } - // 回退到独立 option - $settings = get_option('aksrt_payment_wechatpay', []); - return is_array($settings) ? $settings : []; + $all = AKSRT_Settings::get_all(); + return [ + 'mch_id' => (string) ($all['wechat_merchantid'] ?? ''), + 'key' => (string) ($all['wechat_jsapi'] ?? ''), + 'appid' => (string) ($all['wechat_appid'] ?? ''), + 'appsecret' => (string) ($all['wechat_appsecret'] ?? ''), + 'cert_path' => (string) ($all['wechat_cert_path'] ?? ''), + 'key_path' => (string) ($all['wechat_key_path'] ?? ''), + 'payment_type' => (string) ($all['wechat_payment_type'] ?? 'native'), + ]; } private function create_sdk(array $config): \Yurun\PaySDK\Weixin\SDK { diff --git a/inc/classes/settings/class-aksrt-settings-payment-runtime.php b/inc/classes/settings/class-aksrt-settings-payment-runtime.php index 822bfd16..be69c048 100644 --- a/inc/classes/settings/class-aksrt-settings-payment-runtime.php +++ b/inc/classes/settings/class-aksrt-settings-payment-runtime.php @@ -1,15 +1,22 @@ !empty($settings['pay_balance_s']), - 'mode' => 'manual_review', - 'desc' => sanitize_textarea_field((string) ($settings['pay_balance_desc'] ?? '')), + 'desc' => $desc, 'auto_to_balance' => !isset($settings['pay_auto_to_balance']) || !empty($settings['pay_auto_to_balance']), 'packages_raw' => $packages, 'package_count' => self::count_structured_lines($packages), - 'help_text' => $help_text !== '' ? $help_text : __('当前支持人工确认与测试联调;接入真实网关时复用同一组字段。', 'aksrt'), + 'help_text' => $desc !== '' ? $desc : __('当前支持人工确认与测试联调;接入真实网关时复用同一组字段。', 'aksrt'), 'product' => [ - 'name' => '', - 'price' => 0, - 'tag' => '元', - 'tag_class' => '', + 'name' => sanitize_text_field((string) ($settings['pay_balance_product_name'] ?? '')), + 'price' => max(0, (float) ($settings['pay_balance_price'] ?? 0)), + 'tag' => sanitize_text_field((string) ($settings['pay_balance_tag'] ?? '元')), + 'tag_class' => sanitize_text_field((string) ($settings['pay_balance_tag_class'] ?? '')), 'custom_s' => !isset($settings['pay_balance_product_custom_s']) || !empty($settings['pay_balance_product_custom_s']), 'custom_limit' => max(1, min(99999, (int) ($settings['pay_balance_product_custom_limit'] ?? 9999))), ], @@ -105,9 +111,9 @@ class AKSRT_Settings_Payment_Runtime { 'only_password' => !empty($settings['pay_balance_pass_charge_only_password']), ], 'exchange' => [ - 'enabled' => false, - 'rate' => 100, - 'desc' => '', + 'enabled' => !empty($settings['pay_balance_exchange_s']), + 'rate' => max(1, min(10000, (int) ($settings['pay_balance_exchange_rate'] ?? 100))), + 'desc' => sanitize_textarea_field((string) ($settings['pay_balance_exchange_desc'] ?? '')), ], ]; } @@ -141,22 +147,22 @@ class AKSRT_Settings_Payment_Runtime { } $packages = (string) ($settings['payment_vip_packages'] ?? "30|30|月卡\n365|365|年卡"); - $help_text = trim((string) ($settings['pay_user_vip_desc'] ?? $settings['payment_vip_help'] ?? '')); + $desc = sanitize_textarea_field((string) ($settings['pay_user_vip_desc'] ?? '')); return [ 'enabled' => $enabled, 'name' => $vip1_enabled ? $name1 : $badge, 'badge_text' => $badge, - 'desc' => sanitize_textarea_field((string) ($settings['pay_user_vip_desc'] ?? '')), + 'desc' => $desc, 'packages_raw' => $packages, 'package_count' => self::count_structured_lines($packages), - 'help_text' => $help_text !== '' ? $help_text : __('会员权益以免扣下载和身份展示为主,开通后立即生效。', 'aksrt'), + 'help_text' => $desc !== '' ? $desc : __('会员权益以免扣下载和身份展示为主,开通后立即生效。', 'aksrt'), 'levels' => [ 1 => [ 'enabled' => $vip1_enabled, 'name' => $name1, - 'download_limit' => max(0, min(9999, (int) ($settings['pay_download_limit_vip_1'] ?? $settings['pay_download_limit'] ?? 0))), - 'download_speed' => max(0, min(99999, (int) ($settings['pay_download_speed_vip_1'] ?? $settings['pay_download_speed'] ?? 0))), + 'download_limit' => max(0, min(9999, (int) ($settings['pay_download_limit_vip_1'] ?? 0))), + 'download_speed' => max(0, min(99999, (int) ($settings['pay_download_speed_vip_1'] ?? 0))), ], 2 => [ 'enabled' => $vip2_enabled, @@ -168,7 +174,6 @@ class AKSRT_Settings_Payment_Runtime { 'benefit' => [ 'download_limit' => max(0, min(9999, (int) ($settings['pay_download_limit'] ?? 0))), 'download_speed' => max(0, min(99999, (int) ($settings['pay_download_speed'] ?? 0))), - 'discount' => 100, 'pass_charge' => !empty($settings['pay_vip_pass_charge_s']), 'pass_charge_desc' => sanitize_textarea_field((string) ($settings['pay_vip_pass_charge_desc'] ?? '')), ], @@ -184,14 +189,6 @@ class AKSRT_Settings_Payment_Runtime { 'desc' => sanitize_textarea_field((string) ($settings['vip_upgrade_desc'] ?? '')), 'product' => sanitize_text_field((string) ($settings['vip_upgrade_product'] ?? '')), ], - 'price' => [ - 'month' => 0, - 'year' => 0, - 'permanent' => 0, - 'unit' => 0, - 'unit_tag' => '元', - 'jump' => 0, - ], ]; } @@ -200,13 +197,13 @@ class AKSRT_Settings_Payment_Runtime { */ public static function get_payment_revenue_settings(): array { $settings = AKSRT_Settings::get_all(); - $note = trim((string) ($settings['pay_income_desc_details'] ?? $settings['payment_revenue_note'] ?? '')); + $note = trim((string) ($settings['payment_revenue_note'] ?? '')); 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'] ?? $settings['payment_revenue_ratio_vip1'] ?? 0))), - 'ratio_vip2' => max(0, min(100, (int) ($settings['ratio_vip_2'] ?? $settings['payment_revenue_ratio_vip2'] ?? 0))), + 'ratio_vip1' => max(0, min(100, (int) ($settings['ratio_vip_1'] ?? 0))), + 'ratio_vip2' => max(0, min(100, (int) ($settings['ratio_vip_2'] ?? 0))), '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'] ?? '')), @@ -223,18 +220,18 @@ class AKSRT_Settings_Payment_Runtime { */ public static function get_payment_affiliate_settings(): array { $settings = AKSRT_Settings::get_all(); - $note = trim((string) ($settings['pay_rebate_text_desc'] ?? $settings['payment_affiliate_note'] ?? '')); + $note = trim((string) ($settings['pay_rebate_text_desc'] ?? '')); return [ 'enabled' => !empty($settings['pay_rebate_s']), - 'ratio' => max(0, min(100, (int) ($settings['pay_rebate_ratio'] ?? $settings['payment_affiliate_ratio'] ?? 10))), + 'ratio' => max(0, min(100, (int) ($settings['pay_rebate_ratio'] ?? 10))), 'window_days' => max(1, min(365, (int) ($settings['payment_affiliate_window_days'] ?? 30))), - 'require_paid_order' => true, + 'require_paid_order' => !isset($settings['payment_affiliate_require_paid_order']) || !empty($settings['payment_affiliate_require_paid_order']), 'self_s' => !isset($settings['pay_rebate_self_s']) || !empty($settings['pay_rebate_self_s']), 'show_users' => !isset($settings['pay_rebate_show_users']) || !empty($settings['pay_rebate_show_users']), 'note' => $note !== '' ? $note : __('返佣链路复用邀请体系,订单归因和结算自动衔接当前策略字段。', 'aksrt'), 'user_ratio' => [ - 'user' => max(0, min(100, (int) ($settings['pay_rebate_ratio'] ?? 10))), + 'user' => max(0, min(100, (int) ($settings['payment_affiliate_user_ratio'] ?? $settings['pay_rebate_ratio'] ?? 10))), 'vip1' => max(0, min(100, (int) ($settings['pay_rebate_ratio_vip_1'] ?? 0))), 'vip2' => max(0, min(100, (int) ($settings['pay_rebate_ratio_vip_2'] ?? 0))), ], @@ -303,7 +300,7 @@ class AKSRT_Settings_Payment_Runtime { } /** - * Order display / checkout policy (Zibll pay_* contract). + * Order display / checkout policy (Zibll pay_* + AKSRT order ops). * * @return array */ @@ -347,11 +344,11 @@ class AKSRT_Settings_Payment_Runtime { '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'] ?? $s['payment_service'] ?? '')), - 'icon' => sanitize_text_field((string) ($s['payment_service_icon'] ?? '')), - 'value' => sanitize_text_field((string) ($s['payment_service_value'] ?? '')), - 'qq' => sanitize_text_field((string) ($s['payment_service_qq'] ?? '')), - 'wechat' => esc_url_raw((string) ($s['payment_service_wechat'] ?? '')), + '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'] ?? '')), ], ]; } @@ -401,6 +398,36 @@ class AKSRT_Settings_Payment_Runtime { ]; } + /** + * Whether a registered adapter is admin-enabled. + * Maps gateway ids (wechatpay) to schema toggles (payment_wechat_enabled). + */ + public static function is_gateway_enabled(string $gateway_id): bool { + $settings = AKSRT_Settings::get_all(); + $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', + 'xunhupay' => 'payment_xunhupay_enabled', + 'xhpay' => 'payment_xhpay_enabled', + 'payjs' => 'payment_payjs_enabled', + 'epay' => 'payment_epay_enabled', + 'vmq' => 'payment_vmq_enabled', + 'codepay' => 'payment_codepay_enabled', + ]; + $key = $map[$gateway_id] ?? ''; + if ($key === '') { + return false; + } + if ($gateway_id === 'balance') { + return !empty($settings['payment_balance_enabled']) && !empty($settings['pay_balance_s']); + } + return !empty($settings[$key]); + } + /** * @param string[] $fallback * @return string[] diff --git a/inc/classes/shop/class-aksrt-shop-checkout-service.php b/inc/classes/shop/class-aksrt-shop-checkout-service.php index 1c74a25e..a53f2c3a 100644 --- a/inc/classes/shop/class-aksrt-shop-checkout-service.php +++ b/inc/classes/shop/class-aksrt-shop-checkout-service.php @@ -122,6 +122,12 @@ class AKSRT_Shop_Checkout_Service { 'discount' => $data['discount'], ]; + $expiry_minutes = 30; + if (class_exists('AKSRT_Settings')) { + $order_settings = AKSRT_Settings::get_order_settings(); + $expiry_minutes = max(5, min(1440, (int) ($order_settings['expiry'] ?? 30))); + } + $order_number = AKSRT_Order_Repository::get_instance()->create([ 'user_id' => $user_id, 'product_type' => 'shop_product', @@ -130,7 +136,7 @@ class AKSRT_Shop_Checkout_Service { 'currency' => 'CNY', 'status' => AKSRT_Order::STATUS_PENDING, 'payment_method' => $payment_method, - 'expires_at' => date('Y-m-d H:i:s', strtotime('+10 minutes')), + 'expires_at' => date('Y-m-d H:i:s', strtotime('+' . $expiry_minutes . ' minutes')), 'note' => wp_json_encode($meta_payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ]); @@ -246,14 +252,20 @@ class AKSRT_Shop_Checkout_Service { return ['success' => false, 'message' => __('订单金额无效。', 'aksrt')]; } - $balance = AKSRT_Credits::get_balance($user_id); + $balance = AKSRT_Credits::balance($user_id); if ($balance < $amount) { return ['success' => false, 'message' => sprintf(__('积分不足,需要 %d 积分,当前余额 %d。', 'aksrt'), $amount, $balance)]; } - $deducted = AKSRT_Credits::deduct($amount, $user_id, 'shop_points_pay', sprintf(__('积分购买订单 %s', 'aksrt'), $order_number)); - if (!$deducted) { - return ['success' => false, 'message' => __('积分扣除失败。', 'aksrt')]; + $deducted = AKSRT_Credits::deduct( + $user_id, + $amount, + 'shop_points_pay', + ['order' => $order_number], + sprintf(__('积分购买订单 %s', 'aksrt'), $order_number) + ); + if (is_wp_error($deducted)) { + return ['success' => false, 'message' => $deducted->get_error_message() ?: __('积分扣除失败。', 'aksrt')]; } $repo = AKSRT_Order_Repository::get_instance(); diff --git a/inc/classes/shop/class-aksrt-shop-product-service.php b/inc/classes/shop/class-aksrt-shop-product-service.php index 42196e49..f94096a4 100644 --- a/inc/classes/shop/class-aksrt-shop-product-service.php +++ b/inc/classes/shop/class-aksrt-shop-product-service.php @@ -74,23 +74,68 @@ class AKSRT_Shop_Product_Service { } public function get_payment_method_options(): array { - return [ - 'balance' => [ - 'value' => 'balance', - 'label' => __('站内余额', 'aksrt'), - 'description' => __('即时完成支付并写回订单状态。', 'aksrt'), - ], - 'points' => [ - 'value' => 'points', - 'label' => __('积分兑换', 'aksrt'), + $methods = []; + + if (class_exists('AKSRT_Payment_Gateway')) { + foreach (AKSRT_Payment_Gateway::get_instance()->get_all() as $id => $gateway) { + if (!method_exists($gateway, 'is_configured') || !$gateway->is_configured()) { + continue; + } + $methods[$id] = [ + 'value' => $id, + 'label' => method_exists($gateway, 'get_name') ? (string) $gateway->get_name() : $id, + 'description' => $this->gateway_method_description((string) $id), + ]; + } + } + + if (class_exists('AKSRT_Settings') && AKSRT_Settings::credits_enabled()) { + $methods['points'] = [ + 'value' => 'points', + 'label' => __('积分兑换', 'aksrt'), 'description' => __('使用积分购买积分商品。', 'aksrt'), - ], - 'manual_review' => [ - 'value' => 'manual_review', - 'label' => __('人工确认', 'aksrt'), + ]; + } + + $manual = true; + if (class_exists('AKSRT_Settings')) { + $gw = AKSRT_Settings::get_payment_gateway_settings(); + $manual = !empty($gw['manual_review']) || ($gw['mode'] ?? '') === 'manual_review'; + } + if ($manual) { + $methods['manual_review'] = [ + 'value' => 'manual_review', + 'label' => __('人工确认', 'aksrt'), 'description' => __('保留待支付订单,后续由管理员确认。', 'aksrt'), - ], - ]; + ]; + } + + if (empty($methods)) { + $methods['manual_review'] = [ + 'value' => 'manual_review', + 'label' => __('人工确认', 'aksrt'), + 'description' => __('保留待支付订单,后续由管理员确认。', 'aksrt'), + ]; + } + + return $methods; + } + + private function gateway_method_description(string $id): string { + switch ($id) { + case 'balance': + return __('即时完成支付并写回订单状态。', 'aksrt'); + case 'alipay': + return __('跳转支付宝完成支付。', 'aksrt'); + case 'wechatpay': + return __('微信扫码或跳转完成支付。', 'aksrt'); + case 'paypal': + return __('跳转 PayPal 完成支付。', 'aksrt'); + case 'stripe': + return __('跳转 Stripe Checkout 完成支付。', 'aksrt'); + default: + return ''; + } } public function get_shipping_method_options(): array {