\\d+)', [ [ 'methods' => 'GET', 'callback' => [$this, 'rest_preview'], '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; } public function rest_preview(WP_REST_Request $request) { $topic_id = (int) $request['id']; $uid = get_current_user_id(); $coupon_code = sanitize_text_field((string) ($request->get_param('coupon_code') ?? $request->get_param('coupon') ?? '')); $preview = $this->preview($topic_id, $uid, $coupon_code); if (is_wp_error($preview)) { return $preview; } return new WP_REST_Response($preview, 200); } public function rest_purchase(WP_REST_Request $request) { $topic_id = (int) $request['id']; $uid = get_current_user_id(); if ($uid <= 0) { $post = get_post($topic_id); return new WP_Error('aksrt_need_login', __('请先登录后再购买。', 'aksrt'), [ 'status' => 401, 'login_url' => wp_login_url($post ? get_permalink($post) : home_url('/')), ]); } $params = $request->get_json_params(); if (!is_array($params)) { $params = $request->get_params(); } $payment_method = sanitize_key((string) ($params['payment_method'] ?? 'balance')); if ($payment_method === '' || $payment_method === 'cash') { $payment_method = 'balance'; } $coupon_code = sanitize_text_field((string) ($params['coupon_code'] ?? $params['coupon'] ?? '')); return $this->rest_wrap_result( $this->create_cash_order($topic_id, $uid, $payment_method, $coupon_code) ); } /** * @param mixed $result * @return WP_REST_Response|WP_Error */ private function rest_wrap_result($result) { if (is_wp_error($result)) { return $result; } if (empty($result['success'])) { $code = sanitize_key((string) ($result['code'] ?? 'aksrt_pay_failed')); $status = (int) ($result['status'] ?? 400); return new WP_Error( $code !== '' ? $code : 'aksrt_pay_failed', (string) ($result['message'] ?? __('支付失败', 'aksrt')), array_merge( ['status' => $status > 0 ? $status : 400], is_array($result['data'] ?? null) ? $result['data'] : [] ) ); } return new WP_REST_Response($result, 200); } /** * Cashier preview payload (content-pay field subset for cms-download). * * @return array|WP_Error */ public function preview(int $topic_id, int $user_id = 0, string $coupon_code = '') { $assert = $this->assert_topic($topic_id); if (is_wp_error($assert)) { return $assert; } /** @var WP_Post $post */ $post = $assert['post']; $uid = $user_id > 0 ? $user_id : get_current_user_id(); $coupon_code = sanitize_text_field($coupon_code); if ($uid <= 0) { return [ 'post_id' => $topic_id, 'title' => get_the_title($post), 'permalink' => get_permalink($post), 'pay_modo' => '0', 'pay_type' => 'topic', 'pay_type_label' => __('付费主题', 'aksrt'), 'amount' => 0, 'base' => 0, 'list_amount' => 0, 'payable' => 0, 'currency' => '¥', 'amount_label' => '', 'base_label' => '', 'list_amount_label' => '', 'is_free' => false, 'has_paid_access' => false, 'need_login' => true, 'allow_guest' => false, 'need_vip' => false, 'need_purchase' => true, 'logged_in' => false, 'balance' => 0, 'balance_enough' => false, 'payment_methods' => [], 'login_url' => wp_login_url(get_permalink($post)), 'vip_url' => home_url('/'), 'credits_url' => home_url('/'), 'coupon_allowed' => false, 'coupon_desc' => '', 'coupon_code' => '', 'coupon' => null, 'coupon_error' => '', 'coupon_discount' => 0, 'coupon_discount_label' => '', 'rebate_discount' => 0, 'rebate_discount_label' => '', 'points_deduction_allowed' => false, 'card_pass_allowed' => false, 'price_type' => '', ]; } $priced = $this->resolve_price($topic_id, $uid); $list_amount = round((float) ($priced['amount'] ?? 0), 2); $base = round((float) ($priced['base'] ?? $list_amount), 2); $vip_free = !empty($priced['is_free']) || ($list_amount <= 0 && !empty($priced['is_vip_price'])); $has_paid = $this->user_has_paid($uid, $topic_id); $balance = 0; if (class_exists('AKSRT_Credits')) { $balance = (int) AKSRT_Credits::balance($uid); } $methods = function_exists('aksrt_list_cash_payment_methods') ? aksrt_list_cash_payment_methods(true) : [['id' => 'balance', 'name' => __('站内余额', 'aksrt')]]; $coupon_allowed = $this->is_coupon_allowed($topic_id); $coupon_error = ''; $coupon_discount = 0.0; $coupon_meta = null; $applied_code = ''; $payable = $list_amount; if (!$vip_free && $list_amount > 0) { $discounted = $this->apply_topic_cash_discounts($priced, $topic_id, $uid, $coupon_code, true); if (is_wp_error($discounted)) { $coupon_error = $discounted->get_error_message(); $discounted = $this->apply_topic_cash_discounts($priced, $topic_id, $uid, '', true); } if (!is_wp_error($discounted)) { $list_amount = round((float) ($discounted['list_amount'] ?? $list_amount), 2); $payable = round((float) ($discounted['payable'] ?? $list_amount), 2); $coupon_discount = round((float) ($discounted['coupon_discount'] ?? 0), 2); $coupon_meta = !empty($discounted['coupon']) ? $discounted['coupon'] : null; $applied_code = (string) ($discounted['coupon_code'] ?? ($coupon_meta['code'] ?? '')); if ($coupon_error === '' && !empty($discounted['coupon_error'])) { $coupon_error = (string) $discounted['coupon_error']; } } } elseif ($vip_free) { $payable = 0.0; $list_amount = 0.0; } $is_free = $vip_free || $payable <= 0; $amount_label = $is_free ? __('免费', 'aksrt') : sprintf('¥%s', number_format($payable, 2, '.', '')); $list_label = sprintf('¥%s', number_format($list_amount > 0 ? $list_amount : $base, 2, '.', '')); $base_label = sprintf('¥%s', number_format($base, 2, '.', '')); return [ 'post_id' => $topic_id, 'title' => get_the_title($post), 'permalink' => get_permalink($post), 'pay_modo' => '0', 'pay_type' => 'topic', 'pay_type_label' => __('付费主题', 'aksrt'), 'amount' => $payable, 'base' => $base, 'list_amount' => $list_amount > 0 ? $list_amount : $base, 'list_amount_label' => $list_label, 'payable' => $payable, 'currency' => '¥', 'amount_label' => $amount_label, 'base_label' => $base_label, 'is_free' => $is_free, 'has_paid_access' => $has_paid, 'paid_order' => null, 'pending_order' => null, 'need_login' => false, 'allow_guest' => false, 'need_vip' => false, 'need_purchase' => !$has_paid && !$is_free, 'logged_in' => true, 'balance' => $balance, 'balance_enough' => true, 'payment_methods' => $methods, 'login_url' => wp_login_url(get_permalink($post)), 'vip_url' => home_url('/'), 'credits_url' => home_url('/'), 'coupon_allowed' => $coupon_allowed, 'coupon_desc' => $coupon_allowed ? __('支持优惠码', 'aksrt') : '', 'coupon_code' => $applied_code, 'coupon' => $coupon_meta, 'coupon_error' => $coupon_error, 'coupon_discount' => $coupon_discount, 'coupon_discount_label' => $coupon_discount > 0 ? ('-' . sprintf('¥%s', number_format($coupon_discount, 2, '.', ''))) : '', 'rebate_discount' => 0, 'rebate_discount_label' => '', 'points_deduction_allowed' => false, 'card_pass_allowed' => false, 'price_type' => '', 'vip_level' => (int) ($priced['vip_level'] ?? 0), 'is_vip_price' => !empty($priced['is_vip_price']), ]; } /** * Create cash order and invoke gateway. * * @return array|WP_Error */ public function create_cash_order(int $topic_id, int $user_id, string $payment_method = 'balance', string $coupon_code = '') { if ($user_id <= 0) { return new WP_Error('aksrt_need_login', __('请先登录后再购买。', 'aksrt'), ['status' => 401]); } $assert = $this->assert_topic($topic_id); if (is_wp_error($assert)) { return $assert; } /** @var WP_Post $post */ $post = $assert['post']; if ($this->user_has_paid($user_id, $topic_id)) { return new WP_Error('aksrt_already_paid', __('已购买,可直接查看。', 'aksrt'), ['status' => 400]); } $priced = $this->resolve_price($topic_id, $user_id); $list_amount = round((float) ($priced['amount'] ?? 0), 2); $base = round((float) ($priced['base'] ?? $list_amount), 2); $vip_free = !empty($priced['is_free']) || ($list_amount <= 0 && !empty($priced['is_vip_price'])); $coupon_code = sanitize_text_field($coupon_code); // VIP free (no purchase price) — no coupon stack. if ($vip_free) { return $this->create_free_paid_order($topic_id, $user_id, $post, $priced, [ 'list_amount' => 0.0, 'payable' => 0.0, 'coupon_code' => '', 'coupon_discount' => 0.0, 'coupon' => [], 'message' => __('VIP 免费解锁成功', 'aksrt'), 'paid_note' => __('VIP 免费解锁主题。', 'aksrt'), ]); } if ($list_amount <= 0) { return new WP_Error('aksrt_price_unset', __('价格未设置', 'aksrt'), ['status' => 400]); } $discounted = $this->apply_topic_cash_discounts($priced, $topic_id, $user_id, $coupon_code, false); if (is_wp_error($discounted)) { return $discounted; } $list_amount = round((float) ($discounted['list_amount'] ?? $list_amount), 2); $amount = round((float) ($discounted['payable'] ?? $list_amount), 2); $coupon_discount = round((float) ($discounted['coupon_discount'] ?? 0), 2); $coupon_meta = is_array($discounted['coupon'] ?? null) ? $discounted['coupon'] : []; $coupon_code_norm = (string) ($coupon_meta['code'] ?? $discounted['coupon_code'] ?? $coupon_code); // Free after coupon discount. if ($amount <= 0) { return $this->create_free_paid_order($topic_id, $user_id, $post, $priced, [ 'list_amount' => $list_amount, 'payable' => 0.0, 'coupon_code' => $coupon_code_norm, 'coupon_discount' => $coupon_discount, 'coupon' => $coupon_meta, 'message' => __('无需支付,已可访问。', 'aksrt'), 'paid_note' => __('优惠后无需支付。', 'aksrt'), ]); } 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'; } $pending = $this->find_pending_topic_order($user_id, $topic_id); $can_resume = false; if ($pending && abs((float) ($pending['amount'] ?? 0) - $amount) < 0.001) { $pending_meta = $this->decode_order_note_meta($pending); $pending_coupon = (string) ($pending_meta['coupon_code'] ?? ''); if (is_array($pending_meta['coupon'] ?? null)) { $pending_coupon = (string) ($pending_meta['coupon']['code'] ?? $pending_coupon); } if ($pending_coupon === $coupon_code_norm) { $can_resume = true; } } if ($can_resume) { $order_number = (string) ($pending['order_number'] ?? ''); $order = $pending; } else { $note = wp_json_encode([ 'topic_pay' => 1, 'topic_id' => $topic_id, 'pay_modo' => '0', 'vip_level' => (int) ($priced['vip_level'] ?? 0), 'base' => $base, 'list_amount' => $list_amount, 'amount' => $amount, 'payable' => $amount, 'coupon_code' => $coupon_code_norm, 'coupon_discount' => $coupon_discount, 'coupon' => $coupon_meta, 'ip_address' => function_exists('aksrt_pay_client_ip') ? aksrt_pay_client_ip() : (string) ($_SERVER['REMOTE_ADDR'] ?? ''), ], 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_RESOURCE, 'product_id' => $topic_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]); } } $order['subject'] = sprintf( /* translators: %s: topic title */ __('购买论坛主题「%s」', 'aksrt'), get_the_title($post) ); $order['payment_method'] = $payment_method; $order['amount'] = $amount; if (!class_exists('AKSRT_Payment_Gateway')) { return new WP_Error('aksrt_pay_unavailable', __('支付网关不可用。', 'aksrt'), ['status' => 500]); } $gateway_manager = AKSRT_Payment_Gateway::get_instance(); $gateway = $gateway_manager->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, 'pay_modo' => '0', 'message' => $payment_result['message'] ?? __('请在新页面完成支付。', 'aksrt'), 'reload' => false, 'poll' => true, ], ]; } // Sync gateway (balance): mark paid. $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]); } $this->mark_unlocked($user_id, $topic_id); return [ 'success' => true, 'data' => [ 'order_number' => $order_number, 'amount' => $amount, 'pay_modo' => '0', 'message' => $payment_result['message'] ?? __('支付成功', 'aksrt'), 'reload' => true, ], ]; } /** * On paid: unlock topic + burn coupon (idempotent). */ public function on_order_paid(string $order_number, array $paid_order): void { $product_type = sanitize_key((string) ($paid_order['product_type'] ?? '')); if ($product_type !== AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE) { return; } $topic_id = (int) ($paid_order['product_id'] ?? 0); $user_id = (int) ($paid_order['user_id'] ?? 0); if ($topic_id <= 0 || $user_id <= 0) { return; } $meta = $this->decode_order_note_meta($paid_order); $is_topic_pay = !empty($meta['topic_pay']); if (!$is_topic_pay) { $post = get_post($topic_id); if (!($post instanceof WP_Post) || $post->post_type !== 'aksrt_forum_topic') { return; } } $this->mark_unlocked($user_id, $topic_id); $this->finalize_topic_order_side_effects($order_number, $paid_order); } /** * @return array{post:WP_Post}|WP_Error */ private function assert_topic(int $topic_id) { $post = get_post($topic_id); if (!($post instanceof WP_Post) || $post->post_type !== 'aksrt_forum_topic') { return new WP_Error('aksrt_invalid', __('主题不存在', 'aksrt'), ['status' => 404]); } if ($post->post_status !== 'publish') { return new WP_Error('aksrt_invalid', __('主题不可购买', 'aksrt'), ['status' => 404]); } $visibility = sanitize_key((string) get_post_meta($topic_id, '_aksrt_visibility', true)); if ($visibility !== AKSRT_Forum::VISIBILITY_PAY) { return new WP_Error('aksrt_not_paid', __('该主题不是付费可见内容', 'aksrt'), ['status' => 400]); } return ['post' => $post]; } /** * @return array{amount:float,is_vip_price:bool,is_free:bool,label:string,base:float,vip_set:bool,vip_level:int} */ private function resolve_price(int $topic_id, int $user_id): array { if (class_exists('AKSRT_Forum')) { return AKSRT_Forum::get_instance()->resolve_topic_pay_price($topic_id, $user_id); } return [ 'amount' => max(0.0, (float) get_post_meta($topic_id, '_aksrt_pay_price', true)), 'is_vip_price' => false, 'is_free' => false, 'label' => '', 'base' => max(0.0, (float) get_post_meta($topic_id, '_aksrt_pay_price', true)), 'vip_set' => false, 'vip_level' => 0, ]; } private function user_has_paid(int $user_id, int $topic_id): bool { if (class_exists('AKSRT_Forum')) { return AKSRT_Forum::get_instance()->has_user_paid_topic($user_id, $topic_id); } if (class_exists('AKSRT_Order')) { return AKSRT_Order::get_instance()->has_paid_access( $user_id, AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE, $topic_id ); } return false; } private function mark_unlocked(int $user_id, int $topic_id): void { if (class_exists('AKSRT_Forum') && method_exists('AKSRT_Forum', 'mark_topic_unlocked')) { AKSRT_Forum::get_instance()->mark_topic_unlocked($user_id, $topic_id); return; } if ($user_id <= 0 || $topic_id <= 0) { return; } $unlocked = get_user_meta($user_id, 'aksrt_unlocked_topics', true); if (!is_array($unlocked)) { $unlocked = []; } $unlocked[] = $topic_id; update_user_meta($user_id, 'aksrt_unlocked_topics', array_values(array_unique(array_map('intval', $unlocked)))); } private function is_coupon_allowed(int $topic_id): bool { return (int) get_post_meta($topic_id, '_aksrt_topic_coupon_s', true) === 1; } /** * VIP list price → coupon → payable. No affiliate rebate on topics. * * @param array $priced * @return array|WP_Error */ private function apply_topic_cash_discounts(array $priced, int $topic_id, int $user_id, string $coupon_code = '', bool $soft_coupon = false) { $list_amount = round(max(0.0, (float) ($priced['amount'] ?? 0)), 2); $pay = $list_amount; $coupon_discount = 0.0; $coupon_meta = []; $coupon_error = ''; $coupon_code = sanitize_text_field($coupon_code); if ($coupon_code !== '') { if (!$this->is_coupon_allowed($topic_id)) { $err = new WP_Error('aksrt_coupon_not_allowed', __('当前主题不支持优惠码。', 'aksrt'), ['status' => 400]); if (!$soft_coupon) { return $err; } $coupon_error = $err->get_error_message(); $coupon_code = ''; } elseif (!class_exists('AKSRT_Coupon')) { $err = new WP_Error('aksrt_coupon_unavailable', __('优惠券系统不可用。', 'aksrt'), ['status' => 500]); if (!$soft_coupon) { return $err; } $coupon_error = $err->get_error_message(); $coupon_code = ''; } else { $result = AKSRT_Coupon::get_instance()->validate($coupon_code, $pay, [ 'user_id' => max(0, $user_id), 'product_ids' => [$topic_id], 'context' => 'topic', ]); if (empty($result['valid'])) { $err = new WP_Error( 'aksrt_coupon_error', (string) ($result['message'] ?? __('优惠码不可用。', 'aksrt')), ['status' => 400] ); if (!$soft_coupon) { return $err; } $coupon_error = $err->get_error_message(); $coupon_code = ''; } else { $coupon_discount = round(max(0.0, (float) ($result['discount'] ?? 0)), 2); if ($coupon_discount > $pay) { $coupon_discount = $pay; } $pay = round(max(0.0, $pay - $coupon_discount), 2); $c = is_array($result['coupon'] ?? null) ? $result['coupon'] : []; $coupon_meta = [ 'id' => (int) ($c['id'] ?? 0), 'code' => (string) ($c['code'] ?? $coupon_code), 'type' => (string) ($c['type'] ?? ''), 'value' => (float) ($c['value'] ?? 0), 'discount' => $coupon_discount, ]; $coupon_code = (string) $coupon_meta['code']; } } } return [ 'list_amount' => $list_amount, 'coupon_discount' => $coupon_discount, 'coupon' => $coupon_meta, 'coupon_code' => $coupon_code, 'coupon_error' => $coupon_error, 'payable' => round(max(0.0, $pay), 2), ]; } /** * Burn coupon after paid (idempotent via coupon_usage.order_number). */ private function finalize_topic_order_side_effects(string $order_number, array $order): void { $meta = $this->decode_order_note_meta($order); if (empty($meta['topic_pay'])) { return; } if (($meta['pay_modo'] ?? '') === 'points') { return; } $buyer_id = (int) ($order['user_id'] ?? 0); $coupon = is_array($meta['coupon'] ?? null) ? $meta['coupon'] : []; $coupon_code = sanitize_text_field((string) ($coupon['code'] ?? $meta['coupon_code'] ?? '')); $coupon_discount = (float) ($meta['coupon_discount'] ?? $meta['discount'] ?? ($coupon['discount'] ?? 0)); if ($coupon_code === '' || !class_exists('AKSRT_Coupon') || $this->coupon_usage_exists($order_number)) { return; } $coupon_api = AKSRT_Coupon::get_instance(); $coupon_api->use_coupon($coupon_code); $validated = $coupon_api->validate($coupon_code, max(0.01, $coupon_discount), ['user_id' => $buyer_id]); $coupon_id = (int) ($validated['coupon']['id'] ?? $coupon['id'] ?? 0); if ($coupon_id <= 0 && !empty($coupon['id'])) { $coupon_id = (int) $coupon['id']; } if ($coupon_id > 0) { $coupon_api->log_usage($coupon_id, max(0, $buyer_id), $order_number, max(0.0, $coupon_discount)); } } private function coupon_usage_exists(string $order_number): bool { if ($order_number === '' || !class_exists('AKSRT_DB_Migrator')) { return false; } global $wpdb; $table = AKSRT_DB_Migrator::table('coupon_usage'); if ($table === '') { return false; } $found = $wpdb->get_var($wpdb->prepare( "SELECT id FROM {$table} WHERE order_number = %s LIMIT 1", $order_number )); return !empty($found); } /** * Free VIP or free-after-discount paid order. * * @param array{ * list_amount?:float, * payable?:float, * coupon_code?:string, * coupon_discount?:float, * coupon?:array, * message?:string, * paid_note?:string * } $opts * @return array|WP_Error */ private function create_free_paid_order(int $topic_id, int $user_id, WP_Post $post, array $priced, array $opts = []) { $list_amount = round((float) ($opts['list_amount'] ?? 0), 2); $payable = round((float) ($opts['payable'] ?? 0), 2); $coupon_code = sanitize_text_field((string) ($opts['coupon_code'] ?? '')); $coupon_discount = round((float) ($opts['coupon_discount'] ?? 0), 2); $coupon_meta = is_array($opts['coupon'] ?? null) ? $opts['coupon'] : []; $message = (string) ($opts['message'] ?? __('VIP 免费解锁成功', 'aksrt')); $paid_note = (string) ($opts['paid_note'] ?? __('VIP 免费解锁主题。', 'aksrt')); if (!class_exists('AKSRT_Order') || !class_exists('AKSRT_Order_Repository')) { $this->mark_unlocked($user_id, $topic_id); return [ 'success' => true, 'data' => [ 'order_number' => '', 'amount' => 0, 'pay_modo' => '0', 'free' => true, 'message' => $message, 'reload' => true, ], ]; } $note = wp_json_encode([ 'topic_pay' => 1, 'topic_id' => $topic_id, 'pay_modo' => '0', 'vip_level' => (int) ($priced['vip_level'] ?? 0), 'base' => (float) ($priced['base'] ?? 0), 'list_amount' => $list_amount, 'amount' => $payable, 'payable' => $payable, 'coupon_code' => $coupon_code, 'coupon_discount' => $coupon_discount, 'coupon' => $coupon_meta, 'free' => 1, 'ip_address' => function_exists('aksrt_pay_client_ip') ? aksrt_pay_client_ip() : (string) ($_SERVER['REMOTE_ADDR'] ?? ''), ], 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_RESOURCE, 'product_id' => $topic_id, 'amount' => 0.0, 'currency' => 'CNY', 'status' => AKSRT_Order::STATUS_PENDING, 'payment_method' => 'free', 'note' => is_string($note) ? $note : '', 'expires_at' => gmdate('Y-m-d H:i:s', time() + 30 * MINUTE_IN_SECONDS), ]); if ($order_number === '') { $this->mark_unlocked($user_id, $topic_id); return [ 'success' => true, 'data' => [ 'order_number' => '', 'amount' => 0, 'free' => true, 'message' => $message, 'reload' => true, ], ]; } $paid = AKSRT_Order::get_instance()->update_status($order_number, AKSRT_Order::STATUS_PAID, [ 'payment_method' => 'free', 'payment_transaction_id' => 'free_' . $order_number, 'note' => $paid_note, ]); if (!$paid) { return new WP_Error('aksrt_order_confirm_failed', __('订单确认失败。', 'aksrt'), ['status' => 500]); } $this->mark_unlocked($user_id, $topic_id); return [ 'success' => true, 'data' => [ 'order_number' => $order_number, 'amount' => 0, 'pay_modo' => '0', 'free' => true, 'message' => $message, 'reload' => true, ], ]; } private function find_pending_topic_order(int $user_id, int $topic_id): ?array { if ($user_id <= 0 || $topic_id <= 0 || !class_exists('AKSRT_Order_Repository')) { return null; } $orders = AKSRT_Order_Repository::get_instance()->get_user_orders($user_id, [ 'product_type' => AKSRT_Domain_Model::ORDER_PRODUCT_RESOURCE, 'status' => AKSRT_Domain_Model::ORDER_STATUS_PENDING, 'limit' => 20, 'offset' => 0, ]); foreach ($orders as $order) { if ((int) ($order['product_id'] ?? 0) !== $topic_id) { continue; } $meta = $this->decode_order_note_meta($order); if (!empty($meta['topic_pay']) || $this->is_forum_topic_id($topic_id)) { return $order; } } return null; } private function is_forum_topic_id(int $topic_id): bool { $post = get_post($topic_id); return $post instanceof WP_Post && $post->post_type === 'aksrt_forum_topic'; } private function decode_order_note_meta(array $order): array { $note = (string) ($order['note'] ?? ''); if ($note === '') { return []; } $decoded = json_decode($note, true); return is_array($decoded) ? $decoded : []; } }