0) { update_post_meta($product_id, '_aksrt_stock', max(0, $stock - $qty)); } } public function get_option_image(int $product_id, string $options_key): string { if ($options_key === '' || $options_key === '_default') { return ''; } $groups = $this->get_product_options($product_id); $selections = explode('|', $options_key); foreach ($groups as $i => $group) { $sel = $selections[$i] ?? ''; foreach ($group['values'] as $val) { if ($val['label'] === $sel && !empty($val['image'])) { return $val['image']; } } } return ''; } /* ============================================================ * Auto-delivery (§6.2 自动发货) * * Storage: post_meta '_aksrt_auto_delivery' = [ * 'type' => 'fixed'|'invit_code'|'card_pass'|'opts', * 'content' => '固定内容文本', * 'opts' => [ options_key => '该选项的发货内容' ], * ] * ============================================================ */ public function get_auto_delivery_config(int $product_id): array { $raw = get_post_meta($product_id, '_aksrt_auto_delivery', true); if (!is_array($raw)) { return ['type' => '', 'content' => '', 'opts' => []]; } return [ 'type' => sanitize_key((string) ($raw['type'] ?? '')), 'content' => (string) ($raw['content'] ?? ''), 'opts' => is_array($raw['opts'] ?? null) ? $raw['opts'] : [], ]; } public function execute_auto_delivery(int $product_id, string $options_key, int $qty, int $user_id, string $order_number, int $order_item_id = 0): string { $config = $this->get_auto_delivery_config($product_id); $type = $config['type']; if ($type === 'fixed') { return (string) $config['content']; } if ($type === 'opts') { $key = ($options_key !== '' && $options_key !== '_default') ? $options_key : '_default'; return (string) ($config['opts'][$key] ?? $config['content']); } if ($type === 'card_pass') { return $this->consume_card_keys($product_id, $options_key, $qty, $user_id, $order_number, $order_item_id); } if ($type === 'invit_code') { return $this->create_invite_codes($qty, $user_id, $order_number); } return ''; } private function auto_delivery_succeeded(int $product_id, string $content, int $qty): bool { $config = $this->get_auto_delivery_config($product_id); if (in_array($config['type'], ['card_pass', 'invit_code'], true)) { $lines = array_values(array_filter(array_map('trim', preg_split('/\r\n|\r|\n/', $content) ?: []))); return count($lines) === max(1, $qty) && !in_array($content, [__('卡密已售罄', 'aksrt'), __('邀请系统不可用', 'aksrt')], true); } return trim($content) !== ''; } private function consume_card_keys(int $product_id, string $options_key, int $qty, int $user_id, string $order_number, int $order_item_id): string { global $wpdb; $table = \AKSRT\Core\DBMigrator::table('card_key'); if ($table === '' || $order_item_id <= 0 || $wpdb->query('START TRANSACTION') === false) return ''; $where_opt = ''; $params = [$product_id]; if ($options_key !== '' && $options_key !== '_default') { $where_opt = ' AND options_key = %s'; $params[] = $options_key; } else { $where_opt = " AND (options_key = '' OR options_key = '_default')"; } $params[] = $qty; $keys = $wpdb->get_results($wpdb->prepare( "SELECT id, card_code FROM {$table} WHERE product_id = %d AND status = 'available'{$where_opt} ORDER BY id ASC LIMIT %d FOR UPDATE", ...$params ), ARRAY_A); if (count((array) $keys) !== $qty) { $wpdb->query('ROLLBACK'); return __('卡密已售罄', 'aksrt'); } $codes = []; $ids = []; foreach ($keys as $key) { $codes[] = (string) $key['card_code']; $ids[] = (int) $key['id']; } $placeholders = implode(',', array_fill(0, count($ids), '%d')); $updated = $wpdb->query($wpdb->prepare( "UPDATE {$table} SET status = 'used', order_number = %s, used_by = %d, used_at = %s WHERE status = 'available' AND id IN ({$placeholders})", array_merge([$order_number, $user_id, current_time('mysql')], $ids) )); $content = implode("\n", $codes); $item_table = $this->order_item_table(); $persisted = $item_table !== '' ? $wpdb->update( $item_table, ['auto_delivery_content' => $content], ['id' => $order_item_id, 'order_number' => $order_number], ['%s'], ['%d', '%s'] ) : false; if ($updated !== $qty || $persisted === false || $wpdb->query('COMMIT') === false) { $wpdb->query('ROLLBACK'); return ''; } return $content; } private function create_invite_codes(int $qty, int $user_id, string $order_number): string { if (!class_exists('\AKSRT\Credits\Invite')) { return __('邀请系统不可用', 'aksrt'); } $invite = \AKSRT\Credits\Invite::get_instance(); $codes = []; for ($i = 0; $i < $qty; $i++) { $code = $invite->generate_code($user_id); if ($code) { $codes[] = $code; } } return implode("\n", $codes); } /* ============================================================ * Order Items (§6.5 多商品订单子表) * ============================================================ */ private function order_item_table(): string { return \AKSRT\Core\DBMigrator::table('order_item'); } public function save_order_items(string $order_number, array $items): void { global $wpdb; $table = $this->order_item_table(); if ($table === '') return; foreach ($items as $item) { $wpdb->insert($table, [ 'order_number' => $order_number, 'product_id' => (int) ($item['product_id'] ?? 0), 'product_title' => sanitize_text_field((string) ($item['title'] ?? '')), 'options_key' => sanitize_text_field((string) ($item['options_key'] ?? '')), 'options_label' => sanitize_text_field((string) ($item['options_label'] ?? '')), 'quantity' => max(1, (int) ($item['quantity'] ?? 1)), 'unit_price' => (float) ($item['unit_price'] ?? $item['price'] ?? 0), 'total_price' => (float) ($item['total_price'] ?? $item['line_total'] ?? 0), 'discount_amount' => (float) ($item['discount_amount'] ?? 0), 'shipping_fee' => (float) ($item['shipping_fee'] ?? 0), 'pay_price' => (float) ($item['pay_price'] ?? $item['line_total'] ?? 0), 'shipping_type' => sanitize_key((string) ($item['shipping_type'] ?? 'express')), 'auto_delivery_content'=> ($item['auto_delivery_content'] ?? null), 'discount_hit' => isset($item['applied_discounts']) ? wp_json_encode($item['applied_discounts']) : null, 'gift_data' => isset($item['gifts']) ? wp_json_encode($item['gifts']) : null, 'image' => esc_url_raw((string) ($item['image'] ?? '')), ]); } } public function get_order_items(string $order_number): array { global $wpdb; $table = $this->order_item_table(); if ($table === '') return []; $rows = $wpdb->get_results($wpdb->prepare( "SELECT * FROM {$table} WHERE order_number = %s ORDER BY id ASC", $order_number ), ARRAY_A); return array_map(static function ($row) { return [ 'id' => (int) $row['id'], 'product_id' => (int) $row['product_id'], 'product_title' => (string) $row['product_title'], 'options_key' => (string) $row['options_key'], 'options_label' => (string) $row['options_label'], 'quantity' => (int) $row['quantity'], 'unit_price' => (float) $row['unit_price'], 'total_price' => (float) $row['total_price'], 'discount_amount' => (float) $row['discount_amount'], 'shipping_fee' => (float) $row['shipping_fee'], 'pay_price' => (float) $row['pay_price'], 'shipping_type' => (string) $row['shipping_type'], 'auto_delivery_content' => $row['auto_delivery_content'], 'discount_hit' => json_decode($row['discount_hit'] ?? '', true) ?: [], 'gift_data' => json_decode($row['gift_data'] ?? '', true) ?: [], 'image' => (string) $row['image'], ]; }, (array) $rows); } /* ============================================================ * Enhanced Fulfillment (§6.5 履约链路) * ============================================================ */ public function handle_order_paid_enhanced(string $order_number, array $order): void { $items = $this->get_order_items($order_number); if (empty($items)) { $this->handle_order_paid($order_number, $order); return; } $user_id = (int) ($order['user_id'] ?? 0); $auto_delivery_parts = []; $auto_delivery_successes = 0; foreach ($items as $item) { $pid = (int) $item['product_id']; $qty = max(1, (int) $item['quantity']); $opt_key = (string) $item['options_key']; if ($pid <= 0) continue; $this->deduct_option_stock($pid, $opt_key, $qty); $sales = (int) get_post_meta($pid, '_aksrt_sales_volume', true); update_post_meta($pid, '_aksrt_sales_volume', $sales + $qty); $shipping_type = (string) get_post_meta($pid, '_aksrt_shipping_type', true); if ($shipping_type === 'auto') { $content = $this->execute_auto_delivery($pid, $opt_key, $qty, $user_id, $order_number, (int) $item['id']); if ($content !== '') { $auto_delivery_parts[] = $content; $config = $this->get_auto_delivery_config($pid); $persisted = $config['type'] === 'card_pass' ? true : $this->update_order_item_delivery($item['id'], $content); if ($persisted && $this->auto_delivery_succeeded($pid, $content, $qty)) { $auto_delivery_successes++; } } } } $this->process_rebate($order_number, $order, $items); $this->process_gifts($order_number, $order, $items); if (!empty($auto_delivery_parts)) { $this->send_auto_delivery_notification($user_id, $order_number, $auto_delivery_parts); } if ($auto_delivery_successes === count($items) && class_exists('\AKSRT\Payment\OrderRepository')) { \AKSRT\Payment\OrderRepository::get_instance()->update_status( $order_number, \AKSRT\Payment\Order::STATUS_COMPLETED, ['note' => __('自动发货订单已完成。', 'aksrt')] ); } do_action('aksrt_shop_fulfillment_done', $order_number, $order, $items); } private function update_order_item_delivery(int $item_id, string $content): bool { global $wpdb; $table = $this->order_item_table(); if ($table === '') return false; $updated = $wpdb->update($table, ['auto_delivery_content' => $content], ['id' => $item_id], ['%s'], ['%d']); if ($updated === false) return false; if ($updated === 0) { $stored = $wpdb->get_var($wpdb->prepare("SELECT auto_delivery_content FROM {$table} WHERE id = %d", $item_id)); return is_string($stored) && hash_equals($stored, $content); } return true; } private function send_auto_delivery_notification(int $user_id, string $order_number, array $parts): void { if (!class_exists('AKSRT_Notification_Service')) return; $content = sprintf( __("订单 %s 已自动发货:\n\n%s", 'aksrt'), $order_number, implode("\n---\n", $parts) ); AKSRT_Notification_Service::send($user_id, 'auto_delivery', $content, [ 'order_number' => $order_number, ]); if (function_exists('aksrt_send_event_email')) { $user = get_userdata($user_id); if ($user && !empty($user->user_email)) { aksrt_send_event_email($user->user_email, sprintf(__('订单 %s 自动发货通知', 'aksrt'), $order_number), $content); } } } private function process_rebate(string $order_number, array $order, array $items): void { $user_id = (int) ($order['user_id'] ?? 0); if ($user_id <= 0) return; $referrer_id = 0; if (class_exists('\AKSRT\Credits\Invite')) { $referrer_id = \AKSRT\Credits\Invite::get_instance()->get_referrer($user_id); } if ($referrer_id <= 0) return; $rebate_table = \AKSRT\Core\DBMigrator::table('rebate_log'); if ($rebate_table === '') return; global $wpdb; foreach ($items as $item) { $pid = (int) $item['product_id']; $pay_price = (float) $item['pay_price']; if ($pid <= 0 || $pay_price <= 0) continue; $rebate_rate = (float) get_post_meta($pid, '_aksrt_rebate_rate', true); if ($rebate_rate <= 0 && class_exists('AKSRT\Shop\TermMeta')) { $resolved = TermMeta::resolve_inheritance($pid, 'rebate'); $rebate_rate = (float) ($resolved ?: 0); } if ($rebate_rate <= 0) continue; $amount = round($pay_price * $rebate_rate / 100, 2); if ($amount <= 0) continue; $wpdb->insert($rebate_table, [ 'user_id' => $referrer_id, 'order_number' => $order_number, 'source_user_id' => $user_id, 'amount' => $amount, 'type' => 'order', 'status' => 'pending', 'created_at' => current_time('mysql'), ]); } } private function process_gifts(string $order_number, array $order, array $items): void { $user_id = (int) ($order['user_id'] ?? 0); if ($user_id <= 0) return; $claim_key = '_aksrt_shop_gifts_' . sanitize_key($order_number); if (!add_user_meta($user_id, $claim_key, 'processing', true)) return; foreach ($items as $item) { $gifts = $item['gift_data'] ?? []; if (empty($gifts)) continue; foreach ($gifts as $gift) { $gift_type = (string) ($gift['type'] ?? ''); $gift_value = $gift['value'] ?? 0; if ($gift_type === 'credits' && class_exists('\AKSRT\Credits\Credits') && (int) $gift_value > 0) { \AKSRT\Credits\Credits::add((int) $gift_value, $user_id, 'shop_gift', sprintf(__('订单 %s 赠品', 'aksrt'), $order_number)); } elseif (in_array($gift_type, ['level_integral', 'experience'], true) && class_exists('\AKSRT\User\Level') && (int) $gift_value > 0) { \AKSRT\User\Level::add($user_id, 'shop_gift', (int) $gift_value, true); } elseif ($gift_type === 'vip_days' && (int) $gift_value > 0) { $current = (int) get_user_meta($user_id, 'aksrt_vip_expires', true); $base = max($current, time()); update_user_meta($user_id, 'aksrt_vip_expires', $base + ((int) $gift_value * DAY_IN_SECONDS)); // 赠送 VIP 天数时确保至少 VIP1;不降级已有 VIP2。 $level = (int) get_user_meta($user_id, 'aksrt_vip_level', true); if ($level < 1) { update_user_meta($user_id, 'aksrt_vip_level', 1); } } } } update_user_meta($user_id, $claim_key, '1'); } }