|null */ private $vip_settings_cache = null; /** @var array> */ private $vip_member_cache = []; /** @var array> */ private $vip_catalog_cache = []; /** @var array|null */ private $vip_paid_display_cache = null; /** * Read payment settings once per request. The cache is kept on the * singleton instance and is never shared between users or requests. * * @return array */ private function get_vip_settings_context(): array { if (is_array($this->vip_settings_cache)) { return $this->vip_settings_cache; } $settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_payment_vip_settings() : []; $this->vip_settings_cache = is_array($settings) ? $settings : ['enabled' => false, 'levels' => [], 'renew' => [], 'upgrade' => [], 'points_exchange' => []]; return $this->vip_settings_cache; } /** * Read the current member state once per request and user. * * @return array{level:int,expires:int,is_permanent:bool,state:string,expires_label:string} */ private function get_vip_member_context(int $user_id): array { $user_id = max(0, $user_id); if (array_key_exists($user_id, $this->vip_member_cache)) { return $this->vip_member_cache[$user_id]; } $vip_state = $user_id > 0 && function_exists('aksrt_get_user_vip_state') ? aksrt_get_user_vip_state($user_id) : ['level' => 0, 'expires' => 0, 'state' => 'unopened', 'source' => 'none']; $level = (int) ($vip_state['level'] ?? 0); $expires = max(0, (int) ($vip_state['expires'] ?? 0)); $member_state = 'unopened'; if ($level > 0) { $member_state = $expires <= 0 ? 'permanent' : ($expires < time() ? 'expired' : 'active'); } elseif ($expires > 0 && $expires < time()) { // aksrt_get_user_vip_level() lazily clears expired levels; retain // the observable expired state for this response without reviving it. $member_state = 'expired'; } $state = [ 'level' => max(0, $level), 'expires' => $expires, 'is_permanent' => $level > 0 && $expires <= 0, 'state' => $member_state, 'source' => (string) ($vip_state['source'] ?? 'none'), 'expires_label' => $member_state === 'expired' ? __('已过期', 'aksrt') : $this->format_expires_label($expires, $level), ]; $this->vip_member_cache[$user_id] = $state; return $state; } /** * Cache paid-content display settings alongside the VIP request context. * * @return array */ private function get_paid_display_context(): array { if (is_array($this->vip_paid_display_cache)) { return $this->vip_paid_display_cache; } $settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_paid_content_settings() : []; $this->vip_paid_display_cache = is_array($settings) ? $settings : []; return $this->vip_paid_display_cache; } /** * @return array */ public function get_catalog(int $user_id = 0): array { $user_id = max(0, $user_id); if (array_key_exists($user_id, $this->vip_catalog_cache)) { return $this->vip_catalog_cache[$user_id]; } $vip = $this->get_vip_settings_context(); $member = $this->get_vip_member_context($user_id); $level = (int) $member['level']; $expires = (int) $member['expires']; $is_permanent = !empty($member['is_permanent']); $levels = is_array($vip['levels'] ?? null) ? $vip['levels'] : []; $renew = is_array($vip['renew'] ?? null) ? $vip['renew'] : []; $upgrade = is_array($vip['upgrade'] ?? null) ? $vip['upgrade'] : []; $points_ex = is_array($vip['points_exchange'] ?? null) ? $vip['points_exchange'] : []; $renew_enabled = !isset($renew['enabled']) || !empty($renew['enabled']); $upgrade_enabled = !isset($upgrade['enabled']) || !empty($upgrade['enabled']); $credits_enabled = !class_exists('\AKSRT\Settings\Settings') || \AKSRT\Settings\Settings::credits_enabled(); $points_enabled = !empty($points_ex['enabled']) && $credits_enabled; $level_packages = []; foreach ([1, 2] as $lv) { if (empty($levels[$lv]['enabled'])) { continue; } // Cash products can be disabled per level. $product_s = !isset($levels[$lv]['product_s']) || !empty($levels[$lv]['product_s']); if ($is_permanent && $lv <= $level) { continue; } if ($level > 0 && $lv === $level && !$renew_enabled) { continue; } if ($level > 0 && $lv > $level && !$upgrade_enabled) { continue; } $name = (string) ($levels[$lv]['name'] ?? ('VIP' . $lv)); $items = []; $packages = $product_s ? $this->parse_level_products((string) ($levels[$lv]['products_raw'] ?? '')) : []; foreach ($packages as $idx => $pkg) { $base_price = (float) $pkg['price']; $days = (int) $pkg['days']; $action = 'pay'; if ($level > 0 && $lv === $level) { $action = 'renew'; } elseif ($level > 0 && $lv > $level) { $action = 'upgrade'; } $priced = $this->apply_action_price($base_price, $action, $renew, $upgrade, $lv, $level, $expires, $is_permanent, $days); if ($priced === null) { continue; } $show_price = (float) ($priced['show_price'] ?? 0); if ($show_price <= 0 && !empty($pkg['show_price']) && $action === 'pay') { $show_price = (float) $pkg['show_price']; } $discount_tag = (string) ($priced['tag'] ?? ''); if ($discount_tag === '' && $action === 'pay' && !empty($pkg['tag'])) { $discount_tag = (string) $pkg['tag']; } $days_label = __('永久', 'aksrt'); if ((int) $pkg['time'] > 0) { $days_label = ((string) ($pkg['unit'] ?? 'day') === 'month') ? sprintf(__('%d 个月', 'aksrt'), (int) $pkg['time']) : sprintf(__('%d 天', 'aksrt'), (int) $pkg['time']); } $items[] = [ 'package_id' => $this->encode_package_id($lv, $idx + 1), 'level' => $lv, 'index' => $idx + 1, 'action' => $action, 'price' => (float) $priced['price'], 'base_price' => $base_price, 'show_price' => $show_price, 'discount_tag' => $discount_tag, 'days' => $days, 'time' => (int) ($pkg['time'] ?? 0), 'unit' => (string) ($pkg['unit'] ?? 'day'), 'title' => (string) $pkg['title'], 'price_label' => $this->format_cash_label((float) $priced['price']), 'show_price_label' => $show_price > 0 ? $this->format_cash_label($show_price) : '', 'days_label' => $days_label, 'cta_label' => $action === 'renew' ? __('立即续费', 'aksrt') : ($action === 'upgrade' ? __('立即升级', 'aksrt') : __('立即开通', 'aksrt')), ]; } // Dedicated upgrade product: fixed or remaining-days unit price. if ($level === 1 && $lv === 2 && $upgrade_enabled) { $upgrade_items = $this->build_upgrade_items($user_id, $expires, $is_permanent, (string) ($upgrade['product'] ?? '')); foreach ($upgrade_items as $ui) { $items[] = $ui; } } if ($items === []) { continue; } $level_packages[] = [ 'level' => $lv, 'name' => $name, 'action' => ($level > 0 && $lv === $level) ? 'renew' : (($level > 0 && $lv > $level) ? 'upgrade' : 'pay'), 'items' => $items, ]; } $points_packages = []; if ($points_enabled) { $points_packages = $this->get_points_exchange_packages($vip, $levels); if ($level > 0) { $points_packages = array_values(array_filter( $points_packages, static function (array $package) use ($level, $is_permanent): bool { $target_level = (int) ($package['level'] ?? 0); return $target_level > $level || (!$is_permanent && $target_level === $level); } )); } } $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'); $catalog = [ 'enabled' => !empty($vip['enabled']), 'desc' => (string) ($vip['desc'] ?? ''), 'help_text' => (string) ($vip['help_text'] ?? ''), 'user_level' => $level, 'user_expires' => $expires, 'user_is_permanent' => $is_permanent, 'user_expires_label' => (string) ($member['expires_label'] ?? ''), 'user_points' => $points_balance, 'wallet_balance' => $wallet_balance, 'currency_name' => $currency_name, 'renew_enabled' => $renew_enabled, 'upgrade_enabled' => $upgrade_enabled, 'credits_enabled' => $credits_enabled, 'renew_desc' => (string) ($renew['desc'] ?? ''), 'upgrade_desc' => (string) ($upgrade['desc'] ?? ''), 'level_packages' => $level_packages, 'points_exchange' => [ 'enabled' => $points_enabled, 'desc' => (string) ($points_ex['desc'] ?? ''), 'packages' => $points_packages, ], 'payment_methods' => $methods, 'vip_url' => function_exists('aksrt_get_user_center_tab_url') ? aksrt_get_user_center_tab_url('credits') : (function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url() : home_url('/')), 'logged_in' => $user_id > 0, ]; $this->vip_catalog_cache[$user_id] = $catalog; return $catalog; } /** * Find a package in the server-derived catalog. * * The returned group name is request-local metadata used only for the * order subject; price and entitlement data remain server-derived. * * @param array $catalog * @return array|null */ private function find_catalog_package(array $catalog, int $package_id): ?array { $groups = is_array($catalog['level_packages'] ?? null) ? $catalog['level_packages'] : []; foreach ($groups as $group) { if (!is_array($group)) { continue; } $group_name = (string) ($group['name'] ?? ''); $items = is_array($group['items'] ?? null) ? $group['items'] : []; foreach ($items as $item) { if (!is_array($item) || (int) ($item['package_id'] ?? 0) !== $package_id) { continue; } $item['_level_name'] = $group_name !== '' ? $group_name : ('VIP' . (int) ($item['level'] ?? 1)); return $item; } } return null; } /** * @return array|\WP_Error */ public function purchase(int $user_id, int $package_id, string $payment_method = 'balance') { if ($user_id <= 0) { return new \WP_Error('aksrt_need_login', __('请先登录后再开通会员。', 'aksrt'), ['status' => 401]); } $catalog = $this->get_catalog($user_id); if (empty($catalog['enabled'])) { return new \WP_Error('aksrt_vip_disabled', __('会员功能未启用', 'aksrt'), ['status' => 400]); } $pkg = $this->find_catalog_package($catalog, $package_id); if (!$pkg) { return new \WP_Error('aksrt_invalid_package', __('套餐不存在或当前不可购买', 'aksrt'), ['status' => 400]); } $level = (int) ($pkg['level'] ?? 0); $index = (int) ($pkg['index'] ?? 0); $action = sanitize_key((string) ($pkg['action'] ?? 'pay')); $amount = round((float) ($pkg['price'] ?? 0), 2); $base_price = (float) ($pkg['base_price'] ?? $amount); $days = (int) ($pkg['days'] ?? 0); $pkg_time = array_key_exists('time', $pkg) ? (int) $pkg['time'] : $days; $pkg_unit = sanitize_key((string) ($pkg['unit'] ?? 'day')); if (!in_array($pkg_unit, ['day', 'month'], true)) { $pkg_unit = 'day'; } $discount_tag = (string) ($pkg['discount_tag'] ?? ''); $upgrade_mode = sanitize_key((string) ($pkg['upgrade_mode'] ?? '')); if (in_array($upgrade_mode, ['jump', 'permanent'], true)) { $pkg_time = 0; $days = 0; } if ($level < 1 || $level > 2 || $index < 1 || $action === '') { return new \WP_Error('aksrt_invalid_package', __('套餐无效', 'aksrt'), ['status' => 400]); } if ($amount <= 0) { return new \WP_Error('aksrt_invalid_price', __('套餐价格无效', 'aksrt'), ['status' => 400]); } if (!class_exists('\AKSRT\Payment\Order') || !class_exists('\AKSRT\Payment\OrderRepository')) { return new \WP_Error('aksrt_pay_unavailable', __('支付系统不可用', 'aksrt'), ['status' => 500]); } $payment_method = sanitize_key($payment_method); if ($payment_method === '') { $payment_method = 'balance'; } $note = wp_json_encode([ 'vip_pay' => 1, 'level' => $level, 'days' => $days, 'time' => $pkg_time, 'unit' => $pkg_unit, 'package_index' => $index, 'package_title' => (string) ($pkg['title'] ?? ''), 'package_id' => $package_id, 'action' => $action, 'upgrade_mode' => $upgrade_mode, 'base_price' => $base_price, 'discount_tag' => $discount_tag, ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $expires_at = gmdate('Y-m-d H:i:s', time() + 30 * MINUTE_IN_SECONDS); $order_number = \AKSRT\Payment\OrderRepository::get_instance()->create([ 'user_id' => $user_id, 'product_type' => \AKSRT\Core\DomainModel::ORDER_PRODUCT_VIP, 'product_id' => $package_id, 'amount' => $amount, 'currency' => 'CNY', 'status' => \AKSRT\Payment\Order::STATUS_PENDING, 'payment_method' => $payment_method, 'note' => is_string($note) ? $note : '', 'expires_at' => $expires_at, ]); if ($order_number === '') { return new \WP_Error('aksrt_order_create_failed', __('创建订单失败', 'aksrt'), ['status' => 500]); } // Build the request-local order context from the same server-derived // package used for validation. PaymentOrchestrator still reloads the // persisted row under its attempt lock before touching the gateway. $level_name = (string) ($pkg['_level_name'] ?? ('VIP' . $level)); $action_label = $action === 'renew' ? __('续费', 'aksrt') : ($action === 'upgrade' ? __('升级', 'aksrt') : __('开通', 'aksrt')); $order = [ 'order_number' => $order_number, 'user_id' => $user_id, 'product_type' => \AKSRT\Core\DomainModel::ORDER_PRODUCT_VIP, 'product_id' => $package_id, 'amount' => $amount, 'currency' => 'CNY', 'status' => \AKSRT\Payment\Order::STATUS_PENDING, 'payment_method' => $payment_method, 'note' => is_string($note) ? $note : '', 'expires_at' => $expires_at, 'subject' => sprintf( /* translators: 1: action 2: vip name 3: package title */ __('%1$s%2$s · %3$s', 'aksrt'), $action_label, $level_name, (string) ($pkg['title'] ?? '') ), ]; if (!class_exists('\AKSRT\Payment\Gateway')) { return new \WP_Error('aksrt_pay_unavailable', __('支付网关不可用', 'aksrt'), ['status' => 500]); } $resolved = \AKSRT\Payment\PaymentOrchestrator::resolve_gateway($payment_method); if (empty($resolved['gateway'])) { return new \WP_Error( (string) ($resolved['error_code'] ?? 'aksrt_gateway_unavailable'), (string) ($resolved['message'] ?? __('当前支付方式不可用', 'aksrt')), ['status' => 400, 'order_number' => $order_number] ); } $payment_result = \AKSRT\Payment\PaymentOrchestrator::pay($resolved['gateway'], $order, [ 'paid_note' => __('用户完成会员开通支付。', 'aksrt'), 'order_context' => $order, ]); if (empty($payment_result['success'])) { return new \WP_Error( (string) ($payment_result['code'] ?? 'aksrt_pay_failed'), (string) ($payment_result['message'] ?? __('支付失败', 'aksrt')), ['status' => 402, 'order_number' => $order_number] ); } if (!empty($payment_result['redirect'])) { return [ 'success' => true, 'data' => [ 'redirect' => true, 'payment_url' => (string) ($payment_result['payment_url'] ?? ''), 'qr_content' => (string) ($payment_result['qr_content'] ?? ''), 'order_number' => $order_number, 'amount' => $amount, 'message' => $payment_result['message'], 'reload' => false, 'poll' => true, ], ]; } return [ 'success' => true, 'data' => [ 'order_number' => $order_number, 'amount' => $amount, 'level' => $level, 'days' => $days, 'action' => $action, 'message' => $action === 'renew' ? __('会员续费成功', 'aksrt') : ($action === 'upgrade' ? __('会员升级成功', 'aksrt') : __('会员开通成功', 'aksrt')), 'reload' => true, ], ]; } /** * Activate VIP on paid order. * * @param string $order_number * @param array $paid_order */ public function on_order_paid(string $order_number, array $paid_order): void { if (sanitize_key((string) ($paid_order['product_type'] ?? '')) !== \AKSRT\Core\DomainModel::ORDER_PRODUCT_VIP) { return; } $user_id = (int) ($paid_order['user_id'] ?? 0); if ($user_id <= 0) { return; } $meta = []; if (!empty($paid_order['note']) && is_string($paid_order['note'])) { $decoded = json_decode($paid_order['note'], true); if (is_array($decoded)) { $meta = $decoded; } } if (empty($meta) && is_array($paid_order['meta_payload'] ?? null)) { $meta = $paid_order['meta_payload']; } $level = (int) ($meta['level'] ?? 0); $days = (int) ($meta['days'] ?? 0); $time = (int) ($meta['time'] ?? 0); $unit = sanitize_key((string) ($meta['unit'] ?? 'day')); if (!in_array($unit, ['day', 'month'], true)) { $unit = 'day'; } $upgrade_mode = sanitize_key((string) ($meta['upgrade_mode'] ?? '')); if ($level <= 0) { $decoded = $this->decode_package_id((int) ($paid_order['product_id'] ?? 0)); if ($decoded) { $level = (int) $decoded['level']; } } if ($level < 1) { $level = 1; } if ($level > 2) { $level = 2; } $previous_level = (int) get_user_meta($user_id, 'aksrt_vip_level', true); $previous_expiry = (int) get_user_meta($user_id, 'aksrt_vip_expires', true); $finite_duration = $time > 0 || $days > 0; if ($previous_level > 0 && $previous_expiry === 0 && $level > $previous_level && $finite_duration) { // Preserve a permanent lower tier while a finite higher-tier upgrade is active. update_user_meta($user_id, 'aksrt_vip_base_level', $previous_level); update_user_meta($user_id, 'aksrt_vip_base_expires', 0); } else { // Only a finite higher-tier upgrade may keep a layered base membership. delete_user_meta($user_id, 'aksrt_vip_base_level'); delete_user_meta($user_id, 'aksrt_vip_base_expires'); } $fulfilled = false; // unit upgrade: raise level only, keep current expiry (zibll). if ($upgrade_mode === 'unit') { $this->activate_vip($user_id, $level, 0, true); $fulfilled = (int) get_user_meta($user_id, 'aksrt_vip_level', true) >= $level; } elseif ($time > 0 || (isset($meta['time']) && (int) $meta['time'] === 0 && $days === 0)) { // Prefer calendar time+unit when present (including permanent time=0). $this->activate_vip_duration($user_id, $level, $time, $unit); $new_expiry = (int) get_user_meta($user_id, 'aksrt_vip_expires', true); $expected_base = max($previous_expiry, time()); $expected_expiry = $time <= 0 ? 0 : strtotime('+' . $time . ' ' . ($unit === 'month' ? 'months' : 'days'), $expected_base); if ($expected_expiry === false) { $expected_expiry = $expected_base + ($this->duration_to_days($time, $unit) * DAY_IN_SECONDS); } $fulfilled = (int) get_user_meta($user_id, 'aksrt_vip_level', true) >= max($previous_level, $level) && ($new_expiry === 0 || ($time <= 0 ? $new_expiry === 0 : $new_expiry >= $expected_expiry)); } else { $this->activate_vip($user_id, $level, $days, false); $new_expiry = (int) get_user_meta($user_id, 'aksrt_vip_expires', true); $fulfilled = (int) get_user_meta($user_id, 'aksrt_vip_level', true) >= max($previous_level, $level) && ($new_expiry === 0 || $days <= 0 || $new_expiry >= max($previous_expiry, time()) + ($days * DAY_IN_SECONDS)); } if ($fulfilled && class_exists('\AKSRT\Payment\OrderRepository')) { \AKSRT\Payment\OrderRepository::get_instance()->update_status( $order_number, \AKSRT\Payment\Order::STATUS_COMPLETED, ['note' => __('会员权益已发放。', 'aksrt')] ); } } /** * Apply membership meta. * * @param bool $keep_expiry When true (unit upgrade), only raise level and do not change expiry. * When days<=0 and keep_expiry=false, set permanent (expires=0). */ public function activate_vip(int $user_id, int $level, int $days = 0, bool $keep_expiry = false): void { if ($user_id <= 0) { return; } $level = max(1, min(2, $level)); $current_level = (int) get_user_meta($user_id, 'aksrt_vip_level', true); $new_level = max($current_level, $level); update_user_meta($user_id, 'aksrt_vip_level', $new_level); // Clear expired memory when re-granting. delete_user_meta($user_id, 'aksrt_vip_level_expired'); // Unit upgrade (zibll): keep vip_exp_date, only raise level. if ($keep_expiry) { return; } if ($days <= 0) { // Permanent. delete_user_meta($user_id, 'aksrt_vip_base_level'); delete_user_meta($user_id, 'aksrt_vip_base_expires'); update_user_meta($user_id, 'aksrt_vip_expires', 0); return; } $current_exp = (int) get_user_meta($user_id, 'aksrt_vip_expires', true); if ($current_exp === 0 && $current_level > 0 && $level <= $current_level) { return; // keep an existing permanent level when not upgrading it } $base = max($current_exp, time()); update_user_meta($user_id, 'aksrt_vip_expires', $base + ($days * DAY_IN_SECONDS)); } /** * Activate VIP using calendar duration (zibll month/day). * time=0 → permanent. Extends from max(now, current_exp). */ public function activate_vip_duration(int $user_id, int $level, int $time, string $unit = 'day'): void { if ($user_id <= 0) { return; } $level = max(1, min(2, $level)); $unit = $unit === 'month' ? 'month' : 'day'; $current_level = (int) get_user_meta($user_id, 'aksrt_vip_level', true); $new_level = max($current_level, $level); update_user_meta($user_id, 'aksrt_vip_level', $new_level); delete_user_meta($user_id, 'aksrt_vip_level_expired'); if ($time <= 0) { delete_user_meta($user_id, 'aksrt_vip_base_level'); delete_user_meta($user_id, 'aksrt_vip_base_expires'); update_user_meta($user_id, 'aksrt_vip_expires', 0); return; } $current_exp = (int) get_user_meta($user_id, 'aksrt_vip_expires', true); if ($current_exp === 0 && $current_level > 0 && $level <= $current_level) { return; // keep an existing permanent level when not upgrading it } $base = max($current_exp, time()); $modifier = $unit === 'month' ? ('+' . $time . ' months') : ('+' . $time . ' days'); $new_exp = strtotime($modifier, $base); if ($new_exp === false) { $new_exp = $base + ($this->duration_to_days($time, $unit) * DAY_IN_SECONDS); } update_user_meta($user_id, 'aksrt_vip_expires', (int) $new_exp); } /** * Points exchange. Ordinary users can open a VIP; existing VIP users can * renew the same level or upgrade to a higher level with the exchanged term. * * @return array|\WP_Error */ public function exchange_with_points(int $user_id, int $package_id) { if ($user_id <= 0) { return new \WP_Error('aksrt_need_login', __('请先登录后再兑换。', 'aksrt'), ['status' => 401]); } if (!class_exists('\AKSRT\Credits\Credits')) { return new \WP_Error('aksrt_credits_unavailable', __('积分系统不可用', 'aksrt'), ['status' => 500]); } $catalog = $this->get_catalog($user_id); if (array_key_exists('credits_enabled', $catalog) && empty($catalog['credits_enabled'])) { return new \WP_Error('aksrt_credits_disabled', __('积分功能未启用', 'aksrt'), ['status' => 400]); } $points_ex = is_array($catalog['points_exchange'] ?? null) ? $catalog['points_exchange'] : []; if (empty($points_ex['enabled'])) { return new \WP_Error('aksrt_points_exchange_disabled', __('积分兑换会员未启用', 'aksrt'), ['status' => 400]); } $match = null; $packages = is_array($points_ex['packages'] ?? null) ? $points_ex['packages'] : []; foreach ($packages as $pkg) { if (is_array($pkg) && (int) ($pkg['package_id'] ?? 0) === $package_id) { $match = $pkg; break; } } if (!$match) { return new \WP_Error('aksrt_invalid_package', __('兑换套餐不存在', 'aksrt'), ['status' => 400]); } if ((int) ($catalog['user_level'] ?? 0) > 0) { $current_level = (int) ($catalog['user_level'] ?? 0); $target_level = (int) ($match['level'] ?? 0); $is_permanent = !empty($catalog['user_is_permanent']); if ($target_level < $current_level || ($target_level === $current_level && $is_permanent)) { return new \WP_Error('aksrt_vip_package_unavailable', __('当前会员状态无法兑换该套餐。', 'aksrt'), ['status' => 400]); } } $cost = (int) ($match['points'] ?? 0); $level = (int) ($match['level'] ?? 1); $days = (int) ($match['days'] ?? 0); if ($cost <= 0) { return new \WP_Error('aksrt_invalid_price', __('兑换积分无效', 'aksrt'), ['status' => 400]); } $balance = (int) ($catalog['user_points'] ?? 0); if ($balance < $cost) { return new \WP_Error('aksrt_credits_insufficient', __('积分不足,暂时无法兑换', 'aksrt'), [ 'status' => 402, 'balance' => $balance, 'cost' => $cost, ]); } if (!class_exists('\AKSRT\Payment\Order') || !class_exists('\AKSRT\Payment\OrderRepository')) { return new \WP_Error('aksrt_pay_unavailable', __('订单系统不可用', 'aksrt'), ['status' => 500]); } $note = wp_json_encode([ 'vip_pay' => 1, 'vip_pay_type' => 'points_exchange', 'level' => $level, 'days' => $days, 'points' => $cost, 'package_id' => $package_id, 'package_title' => (string) ($match['title'] ?? ''), 'action' => 'points_exchange', ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $order_number = \AKSRT\Payment\OrderRepository::get_instance()->create([ 'user_id' => $user_id, 'product_type' => \AKSRT\Core\DomainModel::ORDER_PRODUCT_VIP, 'product_id' => $package_id, 'amount' => (float) $cost, 'currency' => 'points', 'status' => \AKSRT\Payment\Order::STATUS_PENDING, 'payment_method' => 'points', 'note' => is_string($note) ? $note : '', 'expires_at' => gmdate('Y-m-d H:i:s', time() + 30 * MINUTE_IN_SECONDS), ]); if ($order_number === '') { return new \WP_Error('aksrt_order_create_failed', __('创建订单失败', 'aksrt'), ['status' => 500]); } $lock = class_exists('\AKSRT\Payment\PaymentAttemptLock') ? \AKSRT\Payment\PaymentAttemptLock::acquire($order_number) : false; if ($lock === false) { return new \WP_Error('aksrt_pay_locked', __('该订单正在发起支付,请稍后重试。', 'aksrt'), ['status' => 409]); } try { $deducted = \AKSRT\Credits\Credits::deduct( $user_id, $cost, 'vip_points_exchange', ['order' => $order_number, 'level' => $level, 'days' => $days], sprintf(__('积分兑换会员 · %s', 'aksrt'), (string) ($match['title'] ?? '')) ); if (is_wp_error($deducted) || $deducted === false) { \AKSRT\Payment\Order::get_instance()->update_status($order_number, \AKSRT\Payment\Order::STATUS_CANCELLED, [ 'note' => __('积分扣除失败,自动取消', 'aksrt'), ]); $msg = is_wp_error($deducted) ? $deducted->get_error_message() : __('积分扣除失败', 'aksrt'); return new \WP_Error('aksrt_credits_deduct_failed', $msg ?: __('积分扣除失败', 'aksrt'), ['status' => 400]); } $paid = \AKSRT\Payment\Order::get_instance()->update_status($order_number, \AKSRT\Payment\Order::STATUS_PAID, [ 'payment_method' => 'points', 'payment_transaction_id' => 'points_' . $order_number, 'note' => sprintf(__('积分兑换会员 %d 积分。', 'aksrt'), $cost), 'require_transition' => true, ]); if (!$paid) { \AKSRT\Credits\Credits::add( $user_id, $cost, 'vip_points_exchange_refund', ['order' => $order_number], __('会员兑换失败退款', 'aksrt') ); return new \WP_Error('aksrt_order_confirm_failed', __('兑换确认失败,已尝试退回积分', 'aksrt'), ['status' => 500]); } } finally { \AKSRT\Payment\PaymentAttemptLock::release($order_number, $lock); } return [ 'success' => true, 'data' => [ 'order_number' => $order_number, 'points' => $cost, 'level' => $level, 'days' => $days, 'balance' => \AKSRT\Credits\Credits::balance($user_id), 'message' => __('兑换成功', 'aksrt'), 'reload' => true, ], ]; } }