'GET', 'callback' => [$this, 'rest_catalog'], 'permission_callback' => [$this, 'check_rest_nonce'], ], [ 'methods' => 'POST', 'callback' => [$this, 'rest_purchase'], 'permission_callback' => [$this, 'check_rest_nonce'], ], ]); } /** * @param WP_REST_Request $request * @return true|WP_Error */ public function check_rest_nonce(WP_REST_Request $request) { $nonce = $request->get_header('x-wp-nonce'); if (!$nonce) { return new WP_Error('aksrt_rest_no_nonce', __('缺少安全令牌', 'aksrt'), ['status' => 403]); } if (!wp_verify_nonce($nonce, 'wp_rest')) { return new WP_Error('aksrt_rest_bad_nonce', __('安全令牌已过期,请刷新页面', 'aksrt'), ['status' => 403]); } return true; } /** * GET /pay/vip */ public function rest_catalog(WP_REST_Request $request) { $uid = get_current_user_id(); return new WP_REST_Response($this->get_catalog($uid), 200); } /** * POST /pay/vip * body: { package_id: int, payment_method?: string } */ public function rest_purchase(WP_REST_Request $request) { $uid = get_current_user_id(); if ($uid <= 0) { return new WP_Error('aksrt_need_login', __('请先登录后再开通会员。', 'aksrt'), [ 'status' => 401, 'login_url' => function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(), ]); } $params = $request->get_json_params(); if (!is_array($params)) { $params = $request->get_params(); } $package_id = (int) ($params['package_id'] ?? 0); $payment_method = sanitize_key((string) ($params['payment_method'] ?? 'balance')); if ($payment_method === '' || $payment_method === 'cash') { $payment_method = 'balance'; } $result = $this->purchase($uid, $package_id, $payment_method); if (is_wp_error($result)) { return $result; } if (empty($result['success'])) { return new WP_Error( sanitize_key((string) ($result['code'] ?? 'aksrt_vip_pay_failed')), (string) ($result['message'] ?? __('开通失败', 'aksrt')), ['status' => (int) ($result['status'] ?? 400)] ); } return new WP_REST_Response($result, 200); } /** * @return array */ public function get_catalog(int $user_id = 0): array { $vip = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_payment_vip_settings() : ['enabled' => false, 'packages_raw' => '', 'levels' => []]; $level = function_exists('aksrt_get_user_vip_level') ? aksrt_get_user_vip_level($user_id) : 0; $expires = $user_id > 0 ? (int) get_user_meta($user_id, 'aksrt_vip_expires', true) : 0; $packages = $this->parse_packages((string) ($vip['packages_raw'] ?? '')); $levels = is_array($vip['levels'] ?? null) ? $vip['levels'] : []; $level_packages = []; foreach ([1, 2] as $lv) { if (empty($levels[$lv]['enabled'])) { continue; } $name = (string) ($levels[$lv]['name'] ?? ('VIP' . $lv)); $items = []; foreach ($packages as $idx => $pkg) { $items[] = [ 'package_id' => $this->encode_package_id($lv, $idx + 1), 'level' => $lv, 'index' => $idx + 1, 'price' => (float) $pkg['price'], 'days' => (int) $pkg['days'], 'title' => (string) $pkg['title'], 'price_label' => $this->format_cash_label((float) $pkg['price']), 'days_label' => ((int) $pkg['days'] <= 0) ? __('永久', 'aksrt') : sprintf( /* translators: %d: days */ __('%d 天', 'aksrt'), (int) $pkg['days'] ), ]; } if ($items === []) { continue; } $level_packages[] = [ 'level' => $lv, 'name' => $name, 'items' => $items, ]; } $methods = $this->available_payment_methods(); return [ 'enabled' => !empty($vip['enabled']), 'desc' => (string) ($vip['desc'] ?? ''), 'help_text' => (string) ($vip['help_text'] ?? ''), 'user_level' => $level, 'user_expires' => $expires, 'user_expires_label' => $this->format_expires_label($expires, $level), 'level_packages' => $level_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, ]; } /** * @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]); } $decoded = $this->decode_package_id($package_id); if (!$decoded) { return new WP_Error('aksrt_invalid_package', __('套餐无效', 'aksrt'), ['status' => 400]); } $level = (int) $decoded['level']; $index = (int) $decoded['index']; $vip = class_exists('AKSRT_Settings') ? AKSRT_Settings::get_payment_vip_settings() : ['enabled' => false, 'packages_raw' => '', 'levels' => []]; if (empty($vip['enabled'])) { return new WP_Error('aksrt_vip_disabled', __('会员功能未启用', 'aksrt'), ['status' => 400]); } $levels = is_array($vip['levels'] ?? null) ? $vip['levels'] : []; if (empty($levels[$level]['enabled'])) { return new WP_Error('aksrt_vip_level_disabled', __('该会员等级未启用', 'aksrt'), ['status' => 400]); } $packages = $this->parse_packages((string) ($vip['packages_raw'] ?? '')); $pkg = $packages[$index - 1] ?? null; if (!$pkg) { return new WP_Error('aksrt_invalid_package', __('套餐不存在', 'aksrt'), ['status' => 400]); } $amount = round((float) $pkg['price'], 2); if ($amount <= 0) { return new WP_Error('aksrt_invalid_price', __('套餐价格无效', 'aksrt'), ['status' => 400]); } if (!class_exists('AKSRT_Order') || !class_exists('AKSRT_Order_Repository')) { return new WP_Error('aksrt_pay_unavailable', __('支付系统不可用', 'aksrt'), ['status' => 500]); } $payment_method = sanitize_key($payment_method); if ($payment_method === '') { $payment_method = 'balance'; } $days = (int) $pkg['days']; $note = wp_json_encode([ 'vip_pay' => 1, 'level' => $level, 'days' => $days, 'package_index' => $index, 'package_title' => (string) $pkg['title'], 'package_id' => $package_id, ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $order_number = AKSRT_Order_Repository::get_instance()->create([ 'user_id' => $user_id, 'product_type' => AKSRT_Domain_Model::ORDER_PRODUCT_VIP, 'product_id' => $package_id, 'amount' => $amount, 'currency' => 'CNY', 'status' => AKSRT_Order::STATUS_PENDING, 'payment_method' => $payment_method, '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]); } $order = AKSRT_Order_Repository::get_instance()->get($order_number); if (!$order) { return new WP_Error('aksrt_order_create_failed', __('创建订单失败', 'aksrt'), ['status' => 500]); } $level_name = (string) ($levels[$level]['name'] ?? ('VIP' . $level)); $order['subject'] = sprintf( /* translators: 1: vip name 2: package title */ __('开通%s · %s', 'aksrt'), $level_name, (string) $pkg['title'] ); $order['payment_method'] = $payment_method; if (!class_exists('AKSRT_Payment_Gateway')) { return new WP_Error('aksrt_pay_unavailable', __('支付网关不可用', 'aksrt'), ['status' => 500]); } $gateway = AKSRT_Payment_Gateway::get_instance()->get_gateway($payment_method); if (!$gateway) { return new WP_Error('aksrt_gateway_unavailable', __('当前支付方式不可用', 'aksrt'), [ 'status' => 400, 'order_number' => $order_number, ]); } if (method_exists($gateway, 'is_configured') && !$gateway->is_configured()) { return new WP_Error('aksrt_gateway_unconfigured', __('该支付方式尚未配置,请联系站长。', 'aksrt'), [ 'status' => 400, 'order_number' => $order_number, ]); } $payment_result = $gateway->create_payment($order); if (empty($payment_result['success'])) { $msg = (string) ($payment_result['message'] ?? __('支付失败', 'aksrt')); $code = ($payment_method === 'balance' && (str_contains($msg, '余额') || str_contains($msg, '不足'))) ? 'aksrt_balance_insufficient' : 'aksrt_pay_failed'; return new WP_Error($code, $msg, [ 'status' => 402, 'order_number' => $order_number, ]); } $code_url = (string) ($payment_result['payment_data']['code_url'] ?? ''); $payment_url = (string) ($payment_result['payment_url'] ?? ''); if ($payment_url !== '' || $code_url !== '') { return [ 'success' => true, 'data' => [ 'redirect' => true, 'payment_url' => $payment_url, 'qr_content' => $code_url, 'order_number' => $order_number, 'amount' => $amount, 'message' => $payment_result['message'] ?? __('请在新页面完成支付。', 'aksrt'), 'reload' => false, 'poll' => true, ], ]; } $paid = AKSRT_Order::get_instance()->update_status($order_number, AKSRT_Order::STATUS_PAID, [ 'payment_method' => $payment_method, 'payment_transaction_id' => sanitize_text_field((string) ($payment_result['transaction_id'] ?? '')), 'note' => __('用户完成会员开通支付。', 'aksrt'), ]); if (!$paid) { return new WP_Error('aksrt_order_confirm_failed', __('订单确认失败', 'aksrt'), ['status' => 500]); } return [ 'success' => true, 'data' => [ 'order_number' => $order_number, 'amount' => $amount, 'level' => $level, 'days' => $days, 'message' => __('会员开通成功', '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_Domain_Model::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); 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; } $this->activate_vip($user_id, $level, $days); } /** * Apply membership meta. */ public function activate_vip(int $user_id, int $level, int $days = 0): 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); if ($days <= 0) { // Permanent. 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) { // Already permanent — keep permanent. return; } $base = max($current_exp, time()); update_user_meta($user_id, 'aksrt_vip_expires', $base + ($days * DAY_IN_SECONDS)); } /** * Parse packages_raw lines: price|days|title * * @return array */ public function parse_packages(string $raw): array { $lines = preg_split('/\r\n|\r|\n/', $raw) ?: []; $out = []; foreach ($lines as $line) { $line = trim((string) $line); if ($line === '') { continue; } $parts = array_map('trim', explode('|', $line)); $price = isset($parts[0]) ? (float) $parts[0] : 0.0; $days = isset($parts[1]) ? (int) $parts[1] : 0; $title = isset($parts[2]) ? sanitize_text_field($parts[2]) : ''; if ($price <= 0) { continue; } if ($title === '') { $title = $days > 0 ? sprintf(__('%d天会员', 'aksrt'), $days) : __('永久会员', 'aksrt'); } $out[] = [ 'price' => round($price, 2), 'days' => max(0, $days), 'title' => $title, ]; } return $out; } public function encode_package_id(int $level, int $index): int { return (max(1, min(2, $level)) * 1000) + max(1, $index); } /** * @return array{level:int,index:int}|null */ public function decode_package_id(int $package_id): ?array { if ($package_id < 1001) { return null; } $level = intdiv($package_id, 1000); $index = $package_id % 1000; if ($level < 1 || $level > 2 || $index < 1) { return null; } return ['level' => $level, 'index' => $index]; } /** * @return array */ private function available_payment_methods(): array { $methods = []; if (!class_exists('AKSRT_Payment_Gateway')) { return [['id' => 'balance', 'name' => __('站内余额', 'aksrt')]]; } $gw = AKSRT_Payment_Gateway::get_instance(); foreach (['balance', 'alipay', 'wechatpay', 'paypal', 'stripe'] as $id) { $g = $gw->get_gateway($id); if (!$g) { continue; } if (method_exists($g, 'is_configured') && !$g->is_configured()) { continue; } $methods[] = [ 'id' => $id, 'name' => method_exists($g, 'get_name') ? (string) $g->get_name() : $id, ]; } if ($methods === []) { $methods[] = ['id' => 'balance', 'name' => __('站内余额', 'aksrt')]; } return $methods; } private function format_cash_label(float $amount): string { $symbol = '¥'; if (class_exists('AKSRT_Settings')) { $paid = AKSRT_Settings::get_paid_content_settings(); $symbol = (string) ($paid['display']['currency_symbol'] ?? '¥'); } $n = function_exists('aksrt_format_price') ? aksrt_format_price($amount) : (string) $amount; return $symbol . $n; } private function format_expires_label(int $expires, int $level): string { if ($level <= 0) { return __('未开通', 'aksrt'); } if ($expires <= 0) { return __('永久有效', 'aksrt'); } if ($expires < time()) { return __('已过期', 'aksrt'); } return sprintf( /* translators: %s: date */ __('到期时间:%s', 'aksrt'), wp_date('Y-m-d H:i', $expires) ); } }