diff --git a/assets/admin/src/schema/sections/payment/gateway.js b/assets/admin/src/schema/sections/payment/gateway.js index 050acb31..9dfd718a 100644 --- a/assets/admin/src/schema/sections/payment/gateway.js +++ b/assets/admin/src/schema/sections/payment/gateway.js @@ -12,7 +12,7 @@ export const gatewaySchema = { payment_wechat_enabled: false, payment_alipay_enabled: false, payment_paypal_enabled: false, - payment_balance_enabled: false, + payment_balance_enabled: true, payment_card_pass_enabled: false, payment_xunhupay_enabled: false, payment_xhpay_enabled: false, @@ -73,7 +73,7 @@ export const gatewaySchema = { { key: 'payment_alipay_enabled', label: '支付宝', type: 'switch', default: false }, { key: 'payment_paypal_enabled', label: 'PayPal', type: 'switch', default: false }, { key: 'payment_stripe_enabled', label: 'Stripe', type: 'switch', default: false }, - { key: 'payment_balance_enabled', label: '余额支付', type: 'switch', default: false }, + { key: 'payment_balance_enabled', label: '余额支付', type: 'switch', default: true, help: '需同时在「余额充值」启用余额功能。关闭后用户中心仍可有钱包,但结账不可用余额支付。' }, { key: 'payment_card_pass_enabled', label: '卡密充值', type: 'switch', default: false }, { key: 'payment_xunhupay_enabled', label: '虎皮椒', type: 'switch', default: false }, { key: 'payment_xhpay_enabled', label: '小火付', type: 'switch', default: false }, diff --git a/assets/admin/src/schema/sections/payment/recharge.js b/assets/admin/src/schema/sections/payment/recharge.js index fd4a2223..d8d773bd 100644 --- a/assets/admin/src/schema/sections/payment/recharge.js +++ b/assets/admin/src/schema/sections/payment/recharge.js @@ -32,9 +32,9 @@ export const rechargeSchema = { title: '充值开关', icon: 'fas fa-wallet', fields: [ - { key: 'pay_balance_s', label: '启用余额功能', type: 'switch', default: true }, + { key: 'pay_balance_s', label: '启用余额功能', type: 'switch', default: true, help: '开启后用户中心显示现金钱包,并默认允许余额支付(可在收款接口单独关闭)。充值需另配支付宝/微信等外部网关。' }, { 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: 'pay_auto_to_balance', label: '自动到账到余额', type: 'switch', default: true, dependsOn: { key: 'pay_balance_s', value: true }, help: '充值订单支付成功后自动入账钱包(卡密/管理员调整不受此开关限制)。' }, ], }, { diff --git a/assets/js/cms-download.js b/assets/js/cms-download.js index 19d6205b..5fea8c08 100644 --- a/assets/js/cms-download.js +++ b/assets/js/cms-download.js @@ -548,18 +548,35 @@ } body += ''; } - let methodHtml = ''; - methods.forEach(function (m, idx) { - const id = escapeAttr(m.id || ''); - const name = escapeHtml(m.name || id); - methodHtml += ''; - }); - if (methodHtml) { - body += '
' + tt('pay_method', '支付方式') + '
' + methodHtml + '
'; - } - body += '
'; - body += ''; - body += '
'; + const walletBal = typeof preview.wallet_balance === 'number' + ? preview.wallet_balance + : (typeof preview.balance === 'number' ? preview.balance : 0); + const hasBalanceMethod = methods.some(function (m) { return (m && m.id) === 'balance'; }); + if (hasBalanceMethod) { + body += '
' + tt('my_wallet', '我的余额') + '¥' + Number(walletBal).toFixed(2) + '
'; + if (preview.balance_enough === false) { + body += '
' + tt('wallet_short', '余额不足,请先充值或选择其他支付方式') + '
'; + if (preview.credits_url) { + body += '
' + tt('go_recharge', '去充值') + '
'; + } + } + } + let methodHtml = ''; + methods.forEach(function (m, idx) { + const id = escapeAttr(m.id || ''); + const name = escapeHtml(m.name || id); + methodHtml += ''; + }); + if (methodHtml) { + body += '
' + tt('pay_method', '支付方式') + '
' + methodHtml + '
'; + } else { + body += '
' + tt('no_pay_method', '暂无可用支付方式,请联系站长配置收款接口') + '
'; + } + body += '
'; + if (methodHtml) { + body += ''; + } + body += '
'; } open('

' + tt('confirm_buy', '确认购买') + '

' + body, returnFocusEl); diff --git a/inc/helpers/aksrt-pay.php b/inc/helpers/aksrt-pay.php index ae88f9d3..e7d48b89 100644 --- a/inc/helpers/aksrt-pay.php +++ b/inc/helpers/aksrt-pay.php @@ -107,12 +107,8 @@ if (!function_exists('aksrt_list_cash_payment_methods')) { ]; } - if ($methods === []) { - $methods[] = $logged_in - ? ['id' => 'balance', 'name' => __('站内余额', 'aksrt')] - : ['id' => 'alipay', 'name' => __('支付宝', 'aksrt')]; - } - + // Do not inject a fake balance/alipay option when nothing is configured — + // callers should surface “暂无可用支付方式” instead of a dead method. return $methods; } } diff --git a/src/AKSRT/Admin/Schema/SchemaPayment.php b/src/AKSRT/Admin/Schema/SchemaPayment.php index 83176572..02324c8a 100644 --- a/src/AKSRT/Admin/Schema/SchemaPayment.php +++ b/src/AKSRT/Admin/Schema/SchemaPayment.php @@ -95,6 +95,10 @@ class SchemaPayment { ['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]], ]], + ['key' => 'withdraw', 'title' => '余额提现', 'fields' => [ + ['key' => 'pay_balance_withdraw_s', 'label' => '启用余额提现', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'pay_balance_s', 'value' => true]], + ['key' => 'pay_balance_withdraw_lowest_money', 'label' => '最低提现金额', 'type' => 'number', 'default' => 50, 'min' => 1, 'max' => 99999, 'dependsOn' => ['key' => 'pay_balance_withdraw_s', 'value' => true]], + ]], ], ], 'vip' => [ @@ -229,7 +233,7 @@ class SchemaPayment { ['key' => 'payment_wechat_enabled', 'label' => '微信支付', 'type' => 'switch', 'default' => false], ['key' => 'payment_alipay_enabled', 'label' => '支付宝', 'type' => 'switch', 'default' => false], ['key' => 'payment_paypal_enabled', 'label' => 'PayPal', 'type' => 'switch', 'default' => false], - ['key' => 'payment_balance_enabled', 'label' => '余额支付', 'type' => 'switch', 'default' => false], + ['key' => 'payment_balance_enabled', 'label' => '余额支付', 'type' => 'switch', 'default' => true], ['key' => 'payment_card_pass_enabled', 'label' => '卡密充值', 'type' => 'switch', 'default' => false], ['key' => 'payment_xunhupay_enabled', 'label' => '虎皮椒', 'type' => 'switch', 'default' => false], ['key' => 'payment_xhpay_enabled', 'label' => '小火付', 'type' => 'switch', 'default' => false], diff --git a/src/AKSRT/Content/PayHelpers.php b/src/AKSRT/Content/PayHelpers.php index 88943e7b..d43d73fc 100644 --- a/src/AKSRT/Content/PayHelpers.php +++ b/src/AKSRT/Content/PayHelpers.php @@ -120,7 +120,14 @@ trait PayHelpers { ? (class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_currency_name() : __('积分', 'aksrt')) : '¥')); - $balance = ($uid > 0 && class_exists('\AKSRT\Credits\Credits')) ? \AKSRT\Credits\Credits::balance($uid) : 0; + // Points cashier uses Credits; cash cashier uses wallet (元). Keep both for UI. + $points_balance = ($uid > 0 && class_exists('\AKSRT\Credits\Credits')) + ? (int) \AKSRT\Credits\Credits::balance($uid) + : 0; + $wallet_balance = ($uid > 0 && class_exists('\AKSRT\Credits\Balance') && \AKSRT\Credits\Balance::enabled()) + ? (float) \AKSRT\Credits\Balance::balance($uid) + : 0.0; + $balance = $pay_modo === 'points' ? $points_balance : $wallet_balance; $methods = $this->available_payment_methods($pay_modo, $uid > 0); $amount_label = $this->format_amount_label($amount, $pay_modo, $currency); @@ -140,6 +147,11 @@ trait PayHelpers { ? aksrt_get_user_free_downloaded_number($uid) : 0; $quota_over = $free_limit > 0 && $free_used >= $free_limit; + $payable_amount = $pay_modo === 'points' ? (int) ceil($payable) : round($payable, 2); + $balance_enough = $pay_modo === 'points' + ? ($points_balance >= (int) ceil($amount)) + : ($wallet_balance + 0.0001 >= (float) $payable_amount); + return [ 'post_id' => $post_id, 'title' => get_the_title($post), @@ -153,7 +165,7 @@ trait PayHelpers { 'base' => $pay_modo === 'points' ? (int) ceil($base) : round($base, 2), 'list_amount' => $pay_modo === 'points' ? (int) ceil($list_amount) : round($list_amount, 2), 'list_amount_label' => $list_label, - 'payable' => $pay_modo === 'points' ? (int) ceil($payable) : round($payable, 2), + 'payable' => $payable_amount, 'original' => (float) ($priced['original'] ?? 0), 'vip1' => (float) ($priced['vip1'] ?? 0), 'vip2' => (float) ($priced['vip2'] ?? 0), @@ -181,7 +193,9 @@ trait PayHelpers { 'need_purchase' => !$has_paid && !$need_vip && $amount > 0, 'logged_in' => $uid > 0, 'balance' => $balance, - 'balance_enough' => $pay_modo !== 'points' || $balance >= (int) ceil($amount), + 'wallet_balance' => $wallet_balance, + 'points_balance' => $points_balance, + 'balance_enough' => $balance_enough, 'payment_methods' => $methods, 'login_url' => wp_login_url(get_permalink($post)), 'vip_url' => $vip_page, diff --git a/src/AKSRT/Content/VipServicePackages.php b/src/AKSRT/Content/VipServicePackages.php index d27527d4..012f9000 100644 --- a/src/AKSRT/Content/VipServicePackages.php +++ b/src/AKSRT/Content/VipServicePackages.php @@ -506,9 +506,12 @@ trait VipServicePackages { * @return array */ private function available_payment_methods(): array { + if (function_exists('aksrt_list_cash_payment_methods')) { + return aksrt_list_cash_payment_methods(true); + } $methods = []; if (!class_exists('\AKSRT\Payment\Gateway')) { - return [['id' => 'balance', 'name' => __('站内余额', 'aksrt')]]; + return $methods; } $gw = \AKSRT\Payment\Gateway::get_instance(); foreach (['balance', 'alipay', 'wechatpay', 'paypal', 'stripe'] as $id) { @@ -524,9 +527,6 @@ trait VipServicePackages { 'name' => method_exists($g, 'get_name') ? (string) $g->get_name() : $id, ]; } - if ($methods === []) { - $methods[] = ['id' => 'balance', 'name' => __('站内余额', 'aksrt')]; - } return $methods; } diff --git a/src/AKSRT/Content/VipServicePurchase.php b/src/AKSRT/Content/VipServicePurchase.php index f8137703..6de268ff 100644 --- a/src/AKSRT/Content/VipServicePurchase.php +++ b/src/AKSRT/Content/VipServicePurchase.php @@ -132,7 +132,12 @@ trait VipServicePurchase { $points_packages = $this->get_points_exchange_packages($vip, $levels); } - $balance = ($user_id > 0 && class_exists('\AKSRT\Credits\Credits')) ? \AKSRT\Credits\Credits::balance($user_id) : 0; + $points_balance = ($user_id > 0 && class_exists('\AKSRT\Credits\Credits')) + ? (int) \AKSRT\Credits\Credits::balance($user_id) + : 0; + $wallet_balance = ($user_id > 0 && class_exists('\AKSRT\Credits\Balance') && \AKSRT\Credits\Balance::enabled()) + ? (float) \AKSRT\Credits\Balance::balance($user_id) + : 0.0; $methods = $this->available_payment_methods(); $currency_name = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_currency_name() : __('积分', 'aksrt'); @@ -144,7 +149,8 @@ trait VipServicePurchase { 'user_expires' => $expires, 'user_is_permanent' => $is_permanent, 'user_expires_label' => $this->format_expires_label($expires, $level), - 'user_points' => $balance, + 'user_points' => $points_balance, + 'wallet_balance' => $wallet_balance, 'currency_name' => $currency_name, 'renew_enabled' => $renew_enabled, 'upgrade_enabled' => $upgrade_enabled, diff --git a/src/AKSRT/Credits/BalanceServiceCharge.php b/src/AKSRT/Credits/BalanceServiceCharge.php index ec6457f6..33d5930f 100644 --- a/src/AKSRT/Credits/BalanceServiceCharge.php +++ b/src/AKSRT/Credits/BalanceServiceCharge.php @@ -81,6 +81,13 @@ trait BalanceServiceCharge { if (!class_exists('AKSRT\\Credits\\Balance')) { return; } + // Honor pay_auto_to_balance (default on). When off, leave order paid without wallet credit. + if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_payment_recharge_settings')) { + $recharge_cfg = \AKSRT\Settings\Settings::get_payment_recharge_settings(); + if (isset($recharge_cfg['auto_to_balance']) && empty($recharge_cfg['auto_to_balance'])) { + return; + } + } $note = json_decode((string) ($order['note'] ?? ''), true); if (!is_array($note)) { $note = []; diff --git a/src/AKSRT/Forum/TopicPay.php b/src/AKSRT/Forum/TopicPay.php index f1d9da09..bcc12416 100644 --- a/src/AKSRT/Forum/TopicPay.php +++ b/src/AKSRT/Forum/TopicPay.php @@ -189,16 +189,14 @@ class TopicPay { } $has_paid = $this->user_has_paid($uid, $topic_id); - $balance = 0; - if ($uid > 0 && class_exists('\AKSRT\Credits\Credits')) { - $balance = (int) \AKSRT\Credits\Credits::balance($uid); + $wallet_balance = 0.0; + if ($uid > 0 && class_exists('\AKSRT\Credits\Balance') && \AKSRT\Credits\Balance::enabled()) { + $wallet_balance = (float) \AKSRT\Credits\Balance::balance($uid); } $methods = function_exists('aksrt_list_cash_payment_methods') ? aksrt_list_cash_payment_methods($uid > 0) - : ($uid > 0 - ? [['id' => 'balance', 'name' => __('站内余额', 'aksrt')]] - : [['id' => 'alipay', 'name' => __('支付宝', 'aksrt')]]); + : []; $coupon_allowed = $this->is_coupon_allowed($topic_id); $coupon_error = ''; @@ -259,8 +257,9 @@ class TopicPay { 'need_vip' => false, 'need_purchase' => !$has_paid && !$is_free, 'logged_in' => $uid > 0, - 'balance' => $balance, - 'balance_enough' => true, + 'balance' => $wallet_balance, + 'wallet_balance' => $wallet_balance, + 'balance_enough' => $is_free || ($wallet_balance + 0.0001 >= (float) $payable), 'payment_methods' => $methods, 'login_url' => wp_login_url(get_permalink($post)), 'vip_url' => home_url('/'), diff --git a/src/AKSRT/Payment/BalanceGateway.php b/src/AKSRT/Payment/BalanceGateway.php index e0ae4cc6..3630049c 100644 --- a/src/AKSRT/Payment/BalanceGateway.php +++ b/src/AKSRT/Payment/BalanceGateway.php @@ -10,7 +10,7 @@ defined('ABSPATH') || exit; /** * 站内余额支付网关(内置实现)。 * - * 优先扣现金钱包(\AKSRT\Credits\Balance / pay_balance);未启用时回退到积分。 + * 仅扣现金钱包(\AKSRT\Credits\Balance / pay_balance_s),不再回退到积分。 */ class BalanceGateway implements GatewayInterface { @@ -24,9 +24,8 @@ class BalanceGateway implements GatewayInterface { } public function is_configured(): bool { - $wallet_on = class_exists('\AKSRT\Credits\Balance') && \AKSRT\Credits\Balance::enabled(); - $credits_on = class_exists('\AKSRT\Credits\Credits'); - if (!$wallet_on && !$credits_on) { + // Cash wallet only — do not treat points as 余额. + if (!class_exists('\AKSRT\Credits\Balance') || !\AKSRT\Credits\Balance::enabled()) { return false; } if (class_exists('\AKSRT\Settings\PaymentRuntime')) { @@ -40,55 +39,26 @@ class BalanceGateway implements GatewayInterface { $amount = round((float) ($order['amount'] ?? 0), 2); $order_number = (string) ($order['order_number'] ?? ''); - // Prefer cash wallet when pay_balance_s is on. - if (class_exists('\AKSRT\Credits\Balance') && \AKSRT\Credits\Balance::enabled()) { - if ($user_id <= 0) { - return ['success' => false, 'message' => __('请先登录', 'aksrt')]; - } - $balance = \AKSRT\Credits\Balance::balance($user_id); - if ($balance < $amount) { - return ['success' => false, 'message' => __('余额不足', 'aksrt')]; - } - $result = \AKSRT\Credits\Balance::deduct( - $user_id, - $amount, - 'order_payment', - ['type' => 'order', 'id' => 0, 'order' => $order_number], - sprintf(__('订单 %s 支付', 'aksrt'), $order_number) - ); - if (is_wp_error($result)) { - return ['success' => false, 'message' => $result->get_error_message() ?: __('扣款失败', 'aksrt')]; - } - return [ - 'success' => true, - 'transaction_id' => 'BAL_' . ($order_number !== '' ? $order_number : (string) time()), - 'message' => __('余额支付成功', 'aksrt'), - ]; + if (!class_exists('\AKSRT\Credits\Balance') || !\AKSRT\Credits\Balance::enabled()) { + return ['success' => false, 'message' => __('余额功能未启用', 'aksrt')]; } - - // Fallback: legacy points-as-balance (1 元 = 100 积分). - if (!class_exists('\AKSRT\Credits\Credits')) { - return ['success' => false, 'message' => __('积分系统未启用', 'aksrt')]; + if ($user_id <= 0) { + return ['success' => false, 'message' => __('请先登录', 'aksrt')]; } - - $credits_cost = (int) ceil($amount * 100); - $balance = \AKSRT\Credits\Credits::balance($user_id); - if ($balance < $credits_cost) { + $balance = \AKSRT\Credits\Balance::balance($user_id); + if ($balance < $amount) { return ['success' => false, 'message' => __('余额不足', 'aksrt')]; } - - $result = \AKSRT\Credits\Credits::deduct( + $result = \AKSRT\Credits\Balance::deduct( $user_id, - $credits_cost, + $amount, 'order_payment', - ['order' => $order_number], + ['type' => 'order', 'id' => 0, 'order' => $order_number], sprintf(__('订单 %s 支付', 'aksrt'), $order_number) ); - if (is_wp_error($result)) { return ['success' => false, 'message' => $result->get_error_message() ?: __('扣款失败', 'aksrt')]; } - return [ 'success' => true, 'transaction_id' => 'BAL_' . ($order_number !== '' ? $order_number : (string) time()), @@ -127,37 +97,20 @@ class BalanceGateway implements GatewayInterface { $user_id = (int) $order['user_id']; $amount = round((float) $amount, 2); - if (class_exists('\AKSRT\Credits\Balance') && \AKSRT\Credits\Balance::enabled()) { - $result = \AKSRT\Credits\Balance::add( - $user_id, - $amount, - 'order_refund', - ['type' => 'order', 'order' => $order_number], - sprintf(__('订单 %s 退款', 'aksrt'), $order_number) - ); - if (is_wp_error($result)) { - return ['success' => false, 'message' => $result->get_error_message() ?: __('退款失败', 'aksrt')]; - } - return ['success' => true, 'refund_id' => 'BAL_REF_' . $order_number, 'message' => __('退款成功', 'aksrt')]; + if (!class_exists('\AKSRT\Credits\Balance') || !\AKSRT\Credits\Balance::enabled()) { + return ['success' => false, 'message' => __('余额功能未启用', 'aksrt')]; } - if (!class_exists('\AKSRT\Credits\Credits')) { - return ['success' => false, 'message' => __('积分系统未启用', 'aksrt')]; - } - - $credits_refund = (int) ceil($amount * 100); - $result = \AKSRT\Credits\Credits::add( + $result = \AKSRT\Credits\Balance::add( $user_id, - $credits_refund, + $amount, 'order_refund', - ['order' => $order_number], + ['type' => 'order', 'order' => $order_number], sprintf(__('订单 %s 退款', 'aksrt'), $order_number) ); - if (is_wp_error($result)) { return ['success' => false, 'message' => $result->get_error_message() ?: __('退款失败', 'aksrt')]; } - return ['success' => true, 'refund_id' => 'BAL_REF_' . $order_number, 'message' => __('退款成功', 'aksrt')]; } } diff --git a/src/AKSRT/Settings/PaymentRuntime.php b/src/AKSRT/Settings/PaymentRuntime.php index 32c6dbb7..85f2a32d 100644 --- a/src/AKSRT/Settings/PaymentRuntime.php +++ b/src/AKSRT/Settings/PaymentRuntime.php @@ -467,7 +467,15 @@ class PaymentRuntime { return false; } if ($gateway_id === 'balance') { - return !empty($settings['payment_balance_enabled']) && !empty($settings['pay_balance_s']); + // 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; } return !empty($settings[$key]); }