diff --git a/assets/css/routes/user-center/credits.css b/assets/css/routes/user-center/credits.css index f4a56337..4f4890eb 100644 --- a/assets/css/routes/user-center/credits.css +++ b/assets/css/routes/user-center/credits.css @@ -6,6 +6,24 @@ box-shadow: none; } +/* Dual assets: wallet + points cards stack with gap */ +.uc-wallet-grid { + margin-bottom: 16px; +} + +.uc-balance-card--wallet .uc-balance-card__bg--wallet, +.uc-balance-card__bg--wallet { + background: linear-gradient(135deg, #0f766e 0%, #14b8a6 45%, #2dd4bf 100%); +} + +.uc-balance-card + .uc-balance-card { + margin-top: 12px; +} + +.uc-transactions--wallet { + margin-bottom: 20px; +} + .uc-content #panel-credits .uc-transactions { background: transparent; border: 0; diff --git a/inc/bootstrap.php b/inc/bootstrap.php index 7571b587..2381d0f5 100644 --- a/inc/bootstrap.php +++ b/inc/bootstrap.php @@ -224,6 +224,8 @@ require_once AKSRT_DIR . '/inc/classes/class-aksrt-db-migrator.php'; // 加载积分服务 require_once AKSRT_DIR . '/inc/classes/class-aksrt-credits.php'; +// 站内现金钱包(与积分分离) +require_once AKSRT_DIR . '/inc/classes/class-aksrt-balance.php'; require_once AKSRT_DIR . '/inc/classes/class-aksrt-credits-earn.php'; require_once AKSRT_DIR . '/inc/classes/class-aksrt-signin.php'; require_once AKSRT_DIR . '/inc/classes/class-aksrt-invite.php'; diff --git a/inc/classes/class-aksrt-after-sale.php b/inc/classes/class-aksrt-after-sale.php index da52cd36..9db233bf 100644 --- a/inc/classes/class-aksrt-after-sale.php +++ b/inc/classes/class-aksrt-after-sale.php @@ -514,15 +514,25 @@ class AKSRT_After_Sale { $payment_method = (string) ($order['payment_method'] ?? 'balance'); $order_number = (string) ($order['order_number'] ?? ''); - // Balance refund: credit back via AKSRT_Credits - if ($payment_method === 'balance' && class_exists('AKSRT_Credits')) { - AKSRT_Credits::add( - $user_id, - (int) round($refund_amount), - sprintf(__('售后退款:订单 %s', 'aksrt'), $order_number), - ['order_number' => $order_number, 'type' => 'refund'], - sprintf(__('售后退款金额 ¥%s', 'aksrt'), number_format($refund_amount, 2)) - ); + // Balance refund: prefer cash wallet, fall back to points. + if ($payment_method === 'balance') { + if (class_exists('AKSRT_Balance') && AKSRT_Balance::enabled()) { + AKSRT_Balance::add( + $user_id, + (float) $refund_amount, + 'order_refund', + ['type' => 'order', 'order' => $order_number], + sprintf(__('售后退款:订单 %s', 'aksrt'), $order_number) + ); + } elseif (class_exists('AKSRT_Credits')) { + AKSRT_Credits::add( + $user_id, + (int) round($refund_amount), + 'order_refund', + ['order_number' => $order_number, 'type' => 'refund'], + sprintf(__('售后退款金额 ¥%s', 'aksrt'), number_format($refund_amount, 2)) + ); + } } // Deduct sales_volume for refunded items diff --git a/inc/classes/class-aksrt-balance.php b/inc/classes/class-aksrt-balance.php new file mode 100644 index 00000000..d8553d77 --- /dev/null +++ b/inc/classes/class-aksrt-balance.php @@ -0,0 +1,277 @@ + $ref + * @return float|WP_Error New balance. + */ + public static function add(int $user_id, float $delta, string $reason, array $ref = [], string $remark = '') { + if ($user_id <= 0) { + return new WP_Error('aksrt_balance_user', __('用户无效', 'aksrt')); + } + $delta = round((float) $delta, 2); + if ($delta <= 0) { + return new WP_Error('aksrt_balance_delta', __('增加余额必须为正数', 'aksrt')); + } + + $new = self::atomic_update($user_id, $delta, false); + if ($new === null) { + return new WP_Error('aksrt_balance_db', __('余额写入失败', 'aksrt')); + } + self::log($user_id, $delta, $new, $reason, $ref, $remark); + do_action('aksrt_balance_added', $user_id, $delta, $reason, $new); + return $new; + } + + /** + * Debit cash wallet. + * + * @param array $ref + * @return float|WP_Error New balance. + */ + public static function deduct(int $user_id, float $cost, string $reason, array $ref = [], string $remark = '') { + if ($user_id <= 0) { + return new WP_Error('aksrt_balance_user', __('用户无效', 'aksrt')); + } + $cost = round((float) $cost, 2); + if ($cost < 0) { + return new WP_Error('aksrt_balance_delta', __('扣减余额必须为非负数', 'aksrt')); + } + if ($cost === 0.0) { + return self::balance($user_id); + } + + $new = self::atomic_update($user_id, $cost, true); + if ($new === null) { + $current = self::balance($user_id); + return new WP_Error('aksrt_balance_insufficient', __('余额不足', 'aksrt'), [ + 'balance' => $current, + 'cost' => $cost, + ]); + } + self::log($user_id, -$cost, $new, $reason, $ref, $remark); + do_action('aksrt_balance_deducted', $user_id, $cost, $reason, $new); + return $new; + } + + /** + * @return float|null + */ + private static function atomic_update(int $user_id, float $abs_delta, bool $is_deduct) { + global $wpdb; + + $abs_delta = round(abs($abs_delta), 2); + $meta_key = self::META_KEY; + $table = $wpdb->usermeta; + + // Ensure meta row exists. + $exists = $wpdb->get_var($wpdb->prepare( + "SELECT umeta_id FROM {$table} WHERE user_id = %d AND meta_key = %s LIMIT 1", + $user_id, + $meta_key + )); + if (!$exists) { + add_user_meta($user_id, $meta_key, '0', true); + } + + if ($is_deduct) { + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $updated = $wpdb->query($wpdb->prepare( + "UPDATE {$table} + SET meta_value = CAST(ROUND(CAST(meta_value AS DECIMAL(18,2)) - %f, 2) AS CHAR) + WHERE user_id = %d AND meta_key = %s + AND CAST(meta_value AS DECIMAL(18,2)) >= %f", + $abs_delta, + $user_id, + $meta_key, + $abs_delta + )); + } else { + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $updated = $wpdb->query($wpdb->prepare( + "UPDATE {$table} + SET meta_value = CAST(ROUND(CAST(meta_value AS DECIMAL(18,2)) + %f, 2) AS CHAR) + WHERE user_id = %d AND meta_key = %s", + $abs_delta, + $user_id, + $meta_key + )); + } + + if ($updated !== 1) { + return null; + } + + wp_cache_delete($user_id, 'user_meta'); + return self::balance($user_id); + } + + /** + * @param array $ref + */ + public static function log(int $user_id, float $delta, float $balance_after, string $reason, array $ref = [], string $remark = ''): void { + global $wpdb; + if (!class_exists('AKSRT_DB_Migrator')) { + return; + } + $table = AKSRT_DB_Migrator::table('balance_log'); + if ($table === '') { + return; + } + + $ip = ''; + if (!empty($_SERVER['REMOTE_ADDR'])) { + $ip = sanitize_text_field(wp_unslash((string) $_SERVER['REMOTE_ADDR'])); + } + $ua = ''; + if (!empty($_SERVER['HTTP_USER_AGENT'])) { + $ua = substr(sanitize_text_field(wp_unslash((string) $_SERVER['HTTP_USER_AGENT'])), 0, 255); + } + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery + $wpdb->insert( + $table, + [ + 'user_id' => $user_id, + 'delta' => round($delta, 2), + 'balance_after' => round($balance_after, 2), + 'reason' => sanitize_key(substr($reason, 0, 64)), + 'remark' => $remark !== '' ? sanitize_text_field(substr($remark, 0, 255)) : null, + 'ref_type' => isset($ref['type']) ? sanitize_key((string) $ref['type']) : null, + 'ref_id' => isset($ref['id']) ? (int) $ref['id'] : (isset($ref['ref_id']) ? (int) $ref['ref_id'] : null), + 'ip' => $ip !== '' ? $ip : null, + 'ua' => $ua !== '' ? $ua : null, + 'created_at' => current_time('mysql', true), + ], + ['%d', '%f', '%f', '%s', '%s', '%s', '%d', '%s', '%s', '%s'] + ); + } + + /** + * @param array $args + * @return array{items:array>,total:int} + */ + public static function get_log(array $args = []): array { + global $wpdb; + if (!class_exists('AKSRT_DB_Migrator')) { + return ['items' => [], 'total' => 0]; + } + $table = AKSRT_DB_Migrator::table('balance_log'); + if ($table === '') { + return ['items' => [], 'total' => 0]; + } + + $user_id = max(0, (int) ($args['user_id'] ?? 0)); + $page = max(1, (int) ($args['page'] ?? 1)); + $per_page = max(1, min(50, (int) ($args['per_page'] ?? 10))); + $offset = ($page - 1) * $per_page; + + $where = '1=1'; + $params = []; + if ($user_id > 0) { + $where .= ' AND user_id = %d'; + $params[] = $user_id; + } + + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $total_sql = "SELECT COUNT(*) FROM {$table} WHERE {$where}"; + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + $total = (int) ($params ? $wpdb->get_var($wpdb->prepare($total_sql, $params)) : $wpdb->get_var($total_sql)); + + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $list_sql = "SELECT * FROM {$table} WHERE {$where} ORDER BY id DESC LIMIT %d OFFSET %d"; + $list_params = array_merge($params, [$per_page, $offset]); + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + $rows = $wpdb->get_results($wpdb->prepare($list_sql, $list_params), ARRAY_A); + if (!is_array($rows)) { + $rows = []; + } + + $items = []; + foreach ($rows as $row) { + $items[] = [ + 'id' => (int) ($row['id'] ?? 0), + 'user_id' => (int) ($row['user_id'] ?? 0), + 'delta' => round((float) ($row['delta'] ?? 0), 2), + 'balance_after' => round((float) ($row['balance_after'] ?? 0), 2), + 'reason' => (string) ($row['reason'] ?? ''), + 'remark' => (string) ($row['remark'] ?? ''), + 'created_at' => (string) ($row['created_at'] ?? ''), + ]; + } + + return ['items' => $items, 'total' => $total]; + } + + /** + * Whether cash wallet feature is enabled (Zibll pay_balance_s). + */ + public static function enabled(): bool { + if (!class_exists('AKSRT_Settings')) { + return false; + } + if (method_exists('AKSRT_Settings', 'get_payment_recharge_settings')) { + $cfg = AKSRT_Settings::get_payment_recharge_settings(); + return !empty($cfg['enabled']); + } + return !empty(AKSRT_Settings::get_all()['pay_balance_s']); + } + + /** + * Format yuan for display. + */ + public static function format(float $amount): string { + return number_format_i18n(round($amount, 2), 2); + } + + /** + * Reason label for ledger UI. + */ + public static function reason_label(string $reason): string { + $map = [ + 'card_pass_balance' => __('卡密充值', 'aksrt'), + 'order_payment' => __('订单支付', 'aksrt'), + 'order_refund' => __('订单退款', 'aksrt'), + 'recharge' => __('余额充值', 'aksrt'), + 'transfer_in' => __('转入', 'aksrt'), + 'transfer_out' => __('转出', 'aksrt'), + 'admin' => __('管理员调整', 'aksrt'), + 'exchange_from_credits' => __('积分兑换', 'aksrt'), + ]; + $key = sanitize_key($reason); + return $map[$key] ?? ($reason !== '' ? $reason : __('余额变动', 'aksrt')); + } +} diff --git a/inc/classes/class-aksrt-charge-card.php b/inc/classes/class-aksrt-charge-card.php index e76105a9..0586870d 100644 --- a/inc/classes/class-aksrt-charge-card.php +++ b/inc/classes/class-aksrt-charge-card.php @@ -419,21 +419,40 @@ class AKSRT_Charge_Card { */ private function fulfill(int $user_id, string $type, array $meta, string $order_number, int $card_id) { if ($type === self::TYPE_BALANCE) { - $amount = (int) round((float) ($meta['amount'] ?? 0)); - if ($amount <= 0 || !class_exists('AKSRT_Credits')) { + $amount = round((float) ($meta['amount'] ?? 0), 2); + if ($amount <= 0) { return new WP_Error('aksrt_card_amount', __('当前卡密可充值金额为0', 'aksrt'), ['status' => 400]); } - $new = AKSRT_Credits::add($user_id, $amount, 'card_pass_balance', [ + // Cash wallet first (Zibll pay_balance); fall back to points if wallet class missing. + if (class_exists('AKSRT_Balance')) { + $new = AKSRT_Balance::add($user_id, $amount, 'card_pass_balance', [ + 'type' => 'charge_card', + 'id' => $card_id, + 'order' => $order_number, + ], sprintf(__('卡密充值 %s 元', 'aksrt'), (string) $amount)); + if (is_wp_error($new)) { + return new WP_Error('aksrt_balance_fail', $new->get_error_message() ?: __('余额入账失败', 'aksrt'), ['status' => 500]); + } + return [ + 'message' => sprintf(__('卡密充值成功,充值金额:%s 元', 'aksrt'), (string) $amount), + 'amount' => (float) $amount, + ]; + } + if (!class_exists('AKSRT_Credits')) { + return new WP_Error('aksrt_card_amount', __('余额系统不可用', 'aksrt'), ['status' => 500]); + } + $pts = (int) round($amount); + $new = AKSRT_Credits::add($user_id, $pts, 'card_pass_balance', [ 'ref_type' => 'charge_card', 'ref_id' => $card_id, 'order' => $order_number, - ], sprintf(__('卡密充值 %d', 'aksrt'), $amount)); - if ($new === null || $new === false) { + ], sprintf(__('卡密充值 %d', 'aksrt'), $pts)); + if ($new === null || $new === false || is_wp_error($new)) { return new WP_Error('aksrt_credit_fail', __('余额入账失败', 'aksrt'), ['status' => 500]); } return [ - 'message' => sprintf(__('卡密充值成功,充值金额:%s', 'aksrt'), (string) $amount), - 'amount' => (float) $amount, + 'message' => sprintf(__('卡密充值成功,充值金额:%s', 'aksrt'), (string) $pts), + 'amount' => (float) $pts, ]; } diff --git a/inc/classes/class-aksrt-db-migrator.php b/inc/classes/class-aksrt-db-migrator.php index f584f8ff..61221d6f 100644 --- a/inc/classes/class-aksrt-db-migrator.php +++ b/inc/classes/class-aksrt-db-migrator.php @@ -19,7 +19,7 @@ class AKSRT_DB_Migrator { use AKSRT_DB_Migrator_Part_3; /** Current schema version; changing it re-runs dbDelta. */ - const SCHEMA_VERSION = '1.14.0'; + const SCHEMA_VERSION = '1.15.0'; const OPT_KEY = 'aksrt_db_schema_version'; diff --git a/inc/classes/class-aksrt-payment-gateway.php b/inc/classes/class-aksrt-payment-gateway.php index 2eabb62a..07f409a5 100644 --- a/inc/classes/class-aksrt-payment-gateway.php +++ b/inc/classes/class-aksrt-payment-gateway.php @@ -338,7 +338,7 @@ class AKSRT_Payment_Gateway { /** * 站内余额支付网关(内置实现) * - * 使用积分/余额进行支付,无需第三方。 + * 优先扣现金钱包(AKSRT_Balance / pay_balance);未启用时回退到积分。 */ class AKSRT_Balance_Gateway implements AKSRT_Payment_Gateway_Interface { @@ -351,7 +351,9 @@ class AKSRT_Balance_Gateway implements AKSRT_Payment_Gateway_Interface { } public function is_configured(): bool { - if (!class_exists('AKSRT_Credits')) { + $wallet_on = class_exists('AKSRT_Balance') && AKSRT_Balance::enabled(); + $credits_on = class_exists('AKSRT_Credits'); + if (!$wallet_on && !$credits_on) { return false; } if (class_exists('AKSRT_Settings_Payment_Runtime')) { @@ -361,17 +363,43 @@ class AKSRT_Balance_Gateway implements AKSRT_Payment_Gateway_Interface { } public function create_payment(array $order): array { + $user_id = (int) ($order['user_id'] ?? 0); + $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_Balance') && AKSRT_Balance::enabled()) { + if ($user_id <= 0) { + return ['success' => false, 'message' => __('请先登录', 'aksrt')]; + } + $balance = AKSRT_Balance::balance($user_id); + if ($balance < $amount) { + return ['success' => false, 'message' => __('余额不足', 'aksrt')]; + } + $result = AKSRT_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'), + ]; + } + + // Fallback: legacy points-as-balance (1 元 = 100 积分). if (!class_exists('AKSRT_Credits')) { return ['success' => false, 'message' => __('积分系统未启用', 'aksrt')]; } - $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'] ?? ''); - + $credits_cost = (int) ceil($amount * 100); $balance = AKSRT_Credits::balance($user_id); - if ($balance < $credits_cost) { return ['success' => false, 'message' => __('余额不足', 'aksrt')]; } @@ -416,10 +444,6 @@ class AKSRT_Balance_Gateway implements AKSRT_Payment_Gateway_Interface { } public function refund(string $order_number, float $amount): array { - if (!class_exists('AKSRT_Credits')) { - return ['success' => false, 'message' => __('积分系统未启用', 'aksrt')]; - } - $order_repo = AKSRT_Order_Repository::get_instance(); $order = $order_repo->get($order_number); @@ -428,8 +452,27 @@ class AKSRT_Balance_Gateway implements AKSRT_Payment_Gateway_Interface { } $user_id = (int) $order['user_id']; - $credits_refund = (int) ceil($amount * 100); + $amount = round((float) $amount, 2); + if (class_exists('AKSRT_Balance') && AKSRT_Balance::enabled()) { + $result = AKSRT_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')) { + return ['success' => false, 'message' => __('积分系统未启用', 'aksrt')]; + } + + $credits_refund = (int) ceil($amount * 100); $result = AKSRT_Credits::add( $user_id, $credits_refund, diff --git a/inc/classes/class-aksrt-user-center.php b/inc/classes/class-aksrt-user-center.php index 2aab5f81..79c5e1fe 100644 --- a/inc/classes/class-aksrt-user-center.php +++ b/inc/classes/class-aksrt-user-center.php @@ -92,8 +92,16 @@ class AKSRT_User_Center { $this->panels = []; foreach ((array) ($settings['tabs'] ?? []) as $key) { - if ($key === 'credits' && (!class_exists('AKSRT_Settings') || !AKSRT_Settings::credits_enabled())) { - continue; + if ($key === 'credits') { + $credits_on = class_exists('AKSRT_Settings') && AKSRT_Settings::credits_enabled(); + $balance_on = class_exists('AKSRT_Balance') && AKSRT_Balance::enabled(); + if (!$credits_on && !$balance_on) { + continue; + } + if ($balance_on) { + $definitions['credits']['label'] = __('我的资产', 'aksrt'); + $definitions['credits']['icon'] = 'wallet'; + } } if (isset($definitions[$key])) { @@ -101,6 +109,18 @@ class AKSRT_User_Center { } } + // Ensure assets panel when wallet and/or credits enabled (even if tabs list omitted credits). + $credits_on = class_exists('AKSRT_Settings') && AKSRT_Settings::credits_enabled(); + $balance_on = class_exists('AKSRT_Balance') && AKSRT_Balance::enabled(); + if (!isset($this->panels['credits']) && ($credits_on || $balance_on)) { + $panel = $definitions['credits']; + if ($balance_on) { + $panel['label'] = __('我的资产', 'aksrt'); + $panel['icon'] = 'wallet'; + } + $this->panels['credits'] = $panel; + } + if (!isset($this->panels['settings'])) { $this->panels['settings'] = $definitions['settings']; } diff --git a/inc/classes/db-traits/trait-aksrt-db-migrator-part-2.php b/inc/classes/db-traits/trait-aksrt-db-migrator-part-2.php index a7d80111..eefa5f6e 100644 --- a/inc/classes/db-traits/trait-aksrt-db-migrator-part-2.php +++ b/inc/classes/db-traits/trait-aksrt-db-migrator-part-2.php @@ -33,6 +33,25 @@ trait AKSRT_DB_Migrator_Part_2 { ) {$charset_collate};"; dbDelta($sql_credit_log); + // ---------- 1b. 现金余额流水(站内钱包,与积分分离) ---------- + $sql_balance_log = "CREATE TABLE {$prefix}aksrt_balance_log ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + user_id BIGINT UNSIGNED NOT NULL, + delta DECIMAL(18,2) NOT NULL, + balance_after DECIMAL(18,2) NOT NULL, + reason VARCHAR(64) NOT NULL, + remark VARCHAR(255) DEFAULT NULL, + ref_type VARCHAR(32) DEFAULT NULL, + ref_id BIGINT UNSIGNED DEFAULT NULL, + ip VARCHAR(64) DEFAULT NULL, + ua VARCHAR(255) DEFAULT NULL, + created_at DATETIME NOT NULL, + PRIMARY KEY (id), + KEY idx_user_time (user_id, created_at), + KEY idx_reason (reason) + ) {$charset_collate};"; + dbDelta($sql_balance_log); + // ---------- 2. 签到 ---------- $sql_signin = "CREATE TABLE {$prefix}aksrt_signin_log ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, diff --git a/inc/classes/db-traits/trait-aksrt-db-migrator-part-3.php b/inc/classes/db-traits/trait-aksrt-db-migrator-part-3.php index 6c9ec448..deb23043 100644 --- a/inc/classes/db-traits/trait-aksrt-db-migrator-part-3.php +++ b/inc/classes/db-traits/trait-aksrt-db-migrator-part-3.php @@ -11,6 +11,7 @@ trait AKSRT_DB_Migrator_Part_3 { global $wpdb; $allowed = [ 'credit_log' => 'aksrt_credit_log', + 'balance_log' => 'aksrt_balance_log', 'signin_log' => 'aksrt_signin_log', 'invite' => 'aksrt_invite', 'dl_token' => 'aksrt_dl_token', diff --git a/inc/helpers/aksrt-user-center.php b/inc/helpers/aksrt-user-center.php index ef0f0aaf..1fa11113 100644 --- a/inc/helpers/aksrt-user-center.php +++ b/inc/helpers/aksrt-user-center.php @@ -432,6 +432,8 @@ if (!function_exists('aksrt_get_user_center_render_context')) { $affiliate_settings = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_payment_affiliate_settings() : []; $show_credit_tabs = (bool) ($dashboard['credits_enabled'] ?? false); $currency_name = (string) ($dashboard['currency_name'] ?? __('积分', 'aksrt')); + $balance_enabled = (bool) ($dashboard['balance_enabled'] ?? false); + $wallet_balance = (float) ($dashboard['wallet_balance'] ?? 0.0); $render_context = [ 'user_center_settings' => $user_center_settings, @@ -470,6 +472,8 @@ if (!function_exists('aksrt_get_user_center_render_context')) { 'currency_name' => $currency_name, 'credit_balance' => (float) ($dashboard['credit_balance'] ?? 0.0), 'credits_enabled' => $show_credit_tabs, + 'balance_enabled' => $balance_enabled, + 'wallet_balance' => $wallet_balance, 'vip_settings' => $vip_settings, 'show_invite_section' => $show_credit_tabs && class_exists('AKSRT_Settings') diff --git a/inc/helpers/aksrt-user-dashboard.php b/inc/helpers/aksrt-user-dashboard.php index ef8ddee8..c3c9d6e4 100644 --- a/inc/helpers/aksrt-user-dashboard.php +++ b/inc/helpers/aksrt-user-dashboard.php @@ -81,6 +81,10 @@ function aksrt_get_user_dashboard_view(int $user_id, array $args = []): array { ? AKSRT_Settings::get_currency_name() : __('积分', 'aksrt'); + // Cash wallet (Zibll pay_balance) — separate from points. + $balance_enabled = $is_own_profile && class_exists('AKSRT_Balance') && AKSRT_Balance::enabled(); + $wallet_balance = $balance_enabled ? AKSRT_Balance::balance($user_id) : 0.0; + $user_home_manager = $include_governance && class_exists('AKSRT_User_Home') ? AKSRT_User_Home::get_instance() : null; @@ -103,6 +107,8 @@ function aksrt_get_user_dashboard_view(int $user_id, array $args = []): array { 'credit_balance' => $credit_balance, 'currency_name' => $currency_name, 'credits_enabled' => $credits_enabled, + 'balance_enabled' => $balance_enabled, + 'wallet_balance' => $wallet_balance, 'is_own_profile' => $is_own_profile, ]; } diff --git a/inc/helpers/user-center-renderers/credits-balance.php b/inc/helpers/user-center-renderers/credits-balance.php index 58c49621..60ff07f2 100644 --- a/inc/helpers/user-center-renderers/credits-balance.php +++ b/inc/helpers/user-center-renderers/credits-balance.php @@ -1,8 +1,8 @@ false, 'desc' => '', 'only_password' => true]; $card_pass_credits = ['enabled' => false, 'desc' => '', 'only_password' => true]; if (class_exists('AKSRT_Charge_Card')) { $opts = AKSRT_Charge_Card::get_instance()->get_public_options(); - $card_pass_balance = is_array($opts['balance'] ?? null) ? $opts['balance'] : $card_pass_balance; $card_pass_credits = is_array($opts['credits'] ?? null) ? $opts['credits'] : $card_pass_credits; -} elseif (class_exists('AKSRT_Settings') && method_exists('AKSRT_Settings', 'get_payment_recharge_settings')) { - $recharge = AKSRT_Settings::get_payment_recharge_settings(); - $pc = is_array($recharge['pass_charge'] ?? null) ? $recharge['pass_charge'] : []; - $card_pass_balance = [ - 'enabled' => !empty($pc['enabled']), - 'desc' => (string) ($pc['desc'] ?? ''), - 'only_password' => !isset($pc['only_password']) || !empty($pc['only_password']), - ]; } ?>
- +
- +
-

-
- -
-
- - -
- -

- - - - - - -

-
- -
diff --git a/inc/helpers/user-center-renderers/credits-wallet.php b/inc/helpers/user-center-renderers/credits-wallet.php new file mode 100644 index 00000000..4512c0b8 --- /dev/null +++ b/inc/helpers/user-center-renderers/credits-wallet.php @@ -0,0 +1,154 @@ + false, 'desc' => '', 'only_password' => true]; +if (class_exists('AKSRT_Charge_Card')) { + $opts = AKSRT_Charge_Card::get_instance()->get_public_options(); + $card_pass_balance = is_array($opts['balance'] ?? null) ? $opts['balance'] : $card_pass_balance; +} elseif (class_exists('AKSRT_Settings') && method_exists('AKSRT_Settings', 'get_payment_recharge_settings')) { + $recharge = AKSRT_Settings::get_payment_recharge_settings(); + $pc = is_array($recharge['pass_charge'] ?? null) ? $recharge['pass_charge'] : []; + $card_pass_balance = [ + 'enabled' => !empty($pc['enabled']), + 'desc' => (string) ($pc['desc'] ?? ''), + 'only_password' => !isset($pc['only_password']) || !empty($pc['only_password']), + ]; +} +?> +
+
+
+
+
+ + +
+
+ + + +
+

+
+
+
+ + +
+
+ + +
+ +

+ + + + + + +

+
+ + +
+
+

+ + +

+
+ (int) $user_id, 'page' => $wallet_page, 'per_page' => 10]); + } else { + $wlog = ['items' => [], 'total' => 0]; + } + if (!empty($wlog['items'])) : + ?> +
+ = 0 ? 'is-positive' : 'is-negative'; + $delta_txt = ($delta > 0 ? '+' : '') . (class_exists('AKSRT_Balance') ? AKSRT_Balance::format($delta) : number_format_i18n($delta, 2)); + ?> +
+
+ +
+
+
+ + + () + +
+
+ +
+
+
+ + + + + + +
+
+ +
+ 1 && function_exists('aksrt_render_profile_pagination')) { + aksrt_render_profile_pagination($total_pages, $wallet_page, 'wallet_page'); + } + ?> + +
+
+ +
+

+
+ +
diff --git a/inc/helpers/user-center-renderers/profile-credits.php b/inc/helpers/user-center-renderers/profile-credits.php index 13fe5f36..a672a239 100644 --- a/inc/helpers/user-center-renderers/profile-credits.php +++ b/inc/helpers/user-center-renderers/profile-credits.php @@ -1,6 +1,8 @@