post_type !== 'post') { return; } if (function_exists('aksrt_bump_content_sales_volume')) { aksrt_bump_content_sales_volume($post_id, 1); } else { $key = \AKSRT\Core\DomainModel::RESOURCE_SALES_VOLUME_KEY; $cur = (int) get_post_meta($post_id, $key, true); update_post_meta($post_id, $key, $cur + 1); } // Guest cookie unlock (user_id=0). $uid = (int) ($paid_order['user_id'] ?? 0); if ($uid <= 0 && function_exists('aksrt_set_guest_content_pay_cookies')) { // Ensure full order row for token material. if (class_exists('\AKSRT\Payment\OrderRepository')) { $full = \AKSRT\Payment\OrderRepository::get_instance()->get($order_number); if (is_array($full)) { $paid_order = array_merge($full, $paid_order); } } aksrt_set_guest_content_pay_cookies($paid_order); } // Consume coupon + write affiliate commission (cash content only; idempotent). $this->finalize_content_order_side_effects($order_number, $paid_order); if (class_exists('\AKSRT\Payment\OrderRepository')) { $completed = \AKSRT\Payment\OrderRepository::get_instance()->update_status( $order_number, \AKSRT\Payment\Order::STATUS_COMPLETED, ['note' => __('内容权益已发放。', 'aksrt')] ); if ($completed && class_exists('\AKSRT\Payment\CreatorRevenue')) { $completed_order = array_merge($paid_order, [ 'order_number' => $order_number, 'status' => \AKSRT\Payment\Order::STATUS_COMPLETED, ]); \AKSRT\Payment\CreatorRevenue::get_instance()->create_from_paid_order($completed_order); } } } /** * On paid: burn coupon (if any) and create affiliate rebate log. */ private function finalize_content_order_side_effects(string $order_number, array $order): void { $meta = $this->decode_order_note_meta($order); if (empty($meta['content_pay'])) { return; } // Points: no coupon burn / no cash commission. if (($meta['pay_modo'] ?? '') === 'points') { return; } $buyer_id = (int) ($order['user_id'] ?? 0); // Coupon burn (shared helper; idempotent via coupon_usage.order_number). if (function_exists('aksrt_burn_order_coupon')) { aksrt_burn_order_coupon($order_number, $meta, $buyer_id); } // Affiliate commission (does not reduce buyer pay). $referrer_id = (int) ($meta['referrer_id'] ?? 0); $rebate_price = round((float) ($meta['rebate_price'] ?? 0), 2); $pay_amount = round((float) ($order['amount'] ?? $meta['payable'] ?? 0), 2); if ($referrer_id <= 0 || $rebate_price <= 0 || $pay_amount <= 0) { return; } if (!class_exists('\AKSRT\Payment\Rebate')) { return; } if ($this->rebate_log_exists($order_number, $referrer_id)) { return; } \AKSRT\Payment\Rebate::get_instance()->create([ 'user_id' => $referrer_id, 'order_number' => $order_number, 'source_user_id' => $buyer_id, 'amount' => $rebate_price, 'type' => \AKSRT\Payment\Rebate::TYPE_ORDER, 'note' => sprintf( /* translators: 1: order number 2: post id */ __('内容推广返佣 订单 %1$s 内容 #%2$d', 'aksrt'), $order_number, (int) ($order['product_id'] ?? $meta['post_id'] ?? 0) ), ]); } /** * @param array $order * @return array */ private function decode_order_note_meta(array $order): array { if (is_array($order['meta_payload'] ?? null) && $order['meta_payload'] !== []) { return $order['meta_payload']; } $note = (string) ($order['note'] ?? ''); if ($note === '') { return []; } $decoded = json_decode($note, true); return is_array($decoded) ? $decoded : []; } private function rebate_log_exists(string $order_number, int $referrer_id): bool { global $wpdb; $table = \AKSRT\Core\DBMigrator::table('rebate_log'); if ($table === '' || $order_number === '') { return false; } $found = $wpdb->get_var($wpdb->prepare( "SELECT id FROM {$table} WHERE order_number = %s AND user_id = %d LIMIT 1", $order_number, $referrer_id )); return !empty($found); } /** * Points one-shot purchase → permanent resource order. * * @return array|\WP_Error */ public function create_and_pay_points(int $post_id, int $user_id, string $price_type = '') { $assert = $this->assert_purchasable($post_id, $user_id, $price_type); if (is_wp_error($assert)) { return $assert; } $priced = $assert['priced']; if ((($priced['modo'] ?? '0') !== 'points')) { return new \WP_Error('aksrt_wrong_modo', __('该内容不支持积分支付。', 'aksrt'), ['status' => 400]); } $amount = (int) ceil((float) ($priced['amount'] ?? 0)); if ($amount <= 0) { return [ 'success' => true, 'data' => [ 'free' => true, 'message' => __('无需支付,已可访问。', 'aksrt'), 'reload' => true, ], ]; } if (!class_exists('\AKSRT\Payment\Order') || !class_exists('\AKSRT\Credits\Credits')) { return new \WP_Error('aksrt_pay_unavailable', __('支付系统不可用。', 'aksrt'), ['status' => 500]); } $balance = \AKSRT\Credits\Credits::balance($user_id); if ($balance < $amount) { return new \WP_Error('aksrt_credits_insufficient', __('积分不足,暂时无法支付。', 'aksrt'), [ 'status' => 402, 'balance' => $balance, 'cost' => $amount, ]); } $note = wp_json_encode([ 'content_pay' => 1, 'pay_modo' => 'points', 'post_id' => $post_id, 'price_type' => $price_type, 'vip_level' => (int) ($priced['vip_level'] ?? 0), 'base' => (float) ($priced['base'] ?? $amount), 'amount' => $amount, ], 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_RESOURCE, 'product_id' => $post_id, 'amount' => (float) $amount, '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, $amount, 'content_points_pay', ['type' => 'post', 'id' => $post_id, 'order' => $order_number], sprintf(__('积分购买《%s》', 'aksrt'), get_the_title($post_id)) ); 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'), $amount), 'require_transition' => true, ]); if (!$paid) { \AKSRT\Credits\Credits::add( $user_id, $amount, 'content_points_pay_refund', ['type' => 'post', 'id' => $post_id, 'order' => $order_number], sprintf(__('购买失败退款:%s', 'aksrt'), get_the_title($post_id)) ); 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, 'amount' => $amount, 'pay_modo' => 'points', 'balance' => \AKSRT\Credits\Credits::balance($user_id), 'message' => __('支付成功', 'aksrt'), 'reload' => true, ], ]; } /** * Cash order create + balance/gateway pay. * * @return array|\WP_Error */ public function create_cash_order(int $post_id, int $user_id, string $payment_method = 'balance', string $price_type = '', string $coupon_code = '') { $assert = $this->assert_purchasable($post_id, $user_id, $price_type); if (is_wp_error($assert)) { return $assert; } $post = $assert['post']; $meta = $assert['meta']; $priced = $assert['priced']; if ((($priced['modo'] ?? '0') === 'points')) { return new \WP_Error('aksrt_wrong_modo', __('该内容仅支持积分支付。', 'aksrt'), ['status' => 400]); } // Card pass cannot stack coupon (zibll). Content cashier does not sell via card_pass. $payment_method = sanitize_key($payment_method); if ($payment_method === 'card_pass') { if (!function_exists('aksrt_is_content_card_pass_pay_allowed') || !aksrt_is_content_card_pass_pay_allowed($post)) { return new \WP_Error( 'aksrt_card_pass_not_allowed', __('内容购买不支持卡密支付,请在用户中心使用卡密兑换余额/会员/积分。', 'aksrt'), ['status' => 400] ); } $coupon_code = ''; } $discounted = $this->apply_cash_discounts($priced, $post, $meta, $user_id, $coupon_code, false); if (is_wp_error($discounted)) { return $discounted; } $amount = round((float) ($discounted['payable'] ?? 0), 2); $list_amount = round((float) ($discounted['list_amount'] ?? $amount), 2); $rebate_discount = round((float) ($discounted['rebate_discount'] ?? 0), 2); $coupon_discount = round((float) ($discounted['coupon_discount'] ?? 0), 2); $coupon_meta = is_array($discounted['coupon'] ?? null) ? $discounted['coupon'] : []; $referrer_id = (int) ($discounted['referrer_id'] ?? 0); $rebate_price = round((float) ($discounted['rebate_price'] ?? 0), 2); if ($amount <= 0) { // Free after discount (or free content): create 0-paid order for access + burn coupon. 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([ 'content_pay' => 1, 'pay_modo' => '0', 'post_id' => $post_id, 'price_type' => $price_type, 'vip_level' => (int) ($priced['vip_level'] ?? 0), 'base' => (float) ($priced['base'] ?? $list_amount), 'list_amount' => $list_amount, 'rebate_discount' => $rebate_discount, 'coupon_discount' => $coupon_discount, 'discount' => $coupon_discount, 'coupon' => $coupon_meta, 'coupon_code' => (string) ($coupon_meta['code'] ?? $coupon_code), 'amount' => 0, 'payable' => 0, 'referrer_id' => $referrer_id, 'rebate_price' => 0, 'ip_address' => function_exists('aksrt_pay_client_ip') ? aksrt_pay_client_ip() : (string) ($_SERVER['REMOTE_ADDR'] ?? ''), 'guest' => $user_id <= 0 ? 1 : 0, ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $order_number = \AKSRT\Payment\OrderRepository::get_instance()->create([ 'user_id' => max(0, $user_id), 'product_type' => \AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE, 'product_id' => $post_id, 'amount' => 0.0, 'currency' => 'CNY', 'status' => \AKSRT\Payment\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 === '') { return new \WP_Error('aksrt_order_create_failed', __('创建订单失败。', 'aksrt'), ['status' => 500]); } $paid = \AKSRT\Payment\Order::get_instance()->update_status($order_number, \AKSRT\Payment\Order::STATUS_PAID, [ 'payment_method' => 'free', 'payment_transaction_id' => 'free_' . $order_number, 'note' => __('优惠后无需支付。', 'aksrt'), ]); if (!$paid) { return new \WP_Error('aksrt_order_confirm_failed', __('订单确认失败。', 'aksrt'), ['status' => 500]); } return [ 'success' => true, 'data' => [ 'order_number' => $order_number, 'amount' => 0, 'pay_modo' => '0', 'free' => true, 'message' => __('无需支付,已可访问。', 'aksrt'), 'reload' => true, ], ]; } if (!class_exists('\AKSRT\Payment\Order') || !class_exists('\AKSRT\Payment\OrderRepository')) { return new \WP_Error('aksrt_pay_unavailable', __('支付系统不可用。', 'aksrt'), ['status' => 500]); } if ($payment_method === '') { $payment_method = $user_id > 0 ? 'balance' : 'alipay'; } if ($user_id <= 0 && in_array($payment_method, ['balance', 'points'], true)) { $payment_method = 'alipay'; } $coupon_code_norm = (string) ($coupon_meta['code'] ?? $coupon_code); // Resume existing pending content order for this post when amount + coupon match (logged-in only). $pending = $user_id > 0 ? $this->find_pending_content_order($user_id, $post_id) : null; $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([ 'content_pay' => 1, 'pay_modo' => '0', 'post_id' => $post_id, 'price_type' => $price_type, 'vip_level' => (int) ($priced['vip_level'] ?? 0), 'base' => (float) ($priced['base'] ?? $list_amount), 'list_amount' => $list_amount, 'rebate_discount' => $rebate_discount, 'coupon_discount' => $coupon_discount, 'discount' => $coupon_discount, 'coupon' => $coupon_meta, 'coupon_code' => $coupon_code_norm, 'amount' => $amount, 'payable' => $amount, 'referrer_id' => $referrer_id, 'rebate_price' => $rebate_price, 'ip_address' => function_exists('aksrt_pay_client_ip') ? aksrt_pay_client_ip() : (string) ($_SERVER['REMOTE_ADDR'] ?? ''), 'guest' => $user_id <= 0 ? 1 : 0, ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); $order_number = \AKSRT\Payment\OrderRepository::get_instance()->create([ 'user_id' => max(0, $user_id), 'product_type' => \AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE, 'product_id' => $post_id, 'amount' => $amount, 'currency' => 'CNY', 'status' => \AKSRT\Payment\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\Payment\OrderRepository::get_instance()->get($order_number); if (!$order) { return new \WP_Error('aksrt_order_create_failed', __('创建订单失败。', 'aksrt'), ['status' => 500]); } } $payment_method = sanitize_key($payment_method ?: (string) ($order['payment_method'] ?? 'balance')); if ($payment_method === '') { $payment_method = $user_id > 0 ? 'balance' : 'alipay'; } $order['subject'] = sprintf( /* translators: %s: post title */ __('购买《%s》', 'aksrt'), get_the_title($post_id) ); $order['payment_method'] = $payment_method; // Ensure gateway uses discounted amount. $order['amount'] = $amount; 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'), ]); 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, 'pay_modo' => '0', 'message' => $payment_result['message'], 'reload' => false, 'poll' => true, ], ]; } return [ 'success' => true, 'data' => [ 'order_number' => $order_number, 'amount' => $amount, 'pay_modo' => '0', 'message' => $payment_result['message'], 'reload' => true, ], ]; } /** * @return array|null */ private function find_pending_content_order(int $user_id, int $post_id): ?array { if ($user_id <= 0 || $post_id <= 0 || !class_exists('\AKSRT\Payment\OrderRepository')) { return null; } $orders = \AKSRT\Payment\OrderRepository::get_instance()->get_user_orders($user_id, [ 'product_type' => \AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE, 'status' => \AKSRT\Core\DomainModel::ORDER_STATUS_PENDING, 'limit' => 20, 'offset' => 0, ]); foreach ($orders as $order) { if ((int) ($order['product_id'] ?? 0) === $post_id) { return $order; } } return null; } }