342 lines
14 KiB
PHP
342 lines
14 KiB
PHP
<?php
|
|
defined('ABSPATH') || exit;
|
|
|
|
trait AKSRT_Shop_Part_3 {
|
|
|
|
|
|
public function deduct_option_stock(int $product_id, string $options_key, int $qty): void {
|
|
$stock_type = (string) get_post_meta($product_id, '_aksrt_stock_type', true);
|
|
if ($stock_type === 'opts' && $options_key !== '' && $options_key !== '_default') {
|
|
$matrix = get_post_meta($product_id, '_aksrt_stock_opts', true);
|
|
if (is_array($matrix) && isset($matrix[$options_key])) {
|
|
$matrix[$options_key] = max(0, (int) $matrix[$options_key] - $qty);
|
|
update_post_meta($product_id, '_aksrt_stock_opts', $matrix);
|
|
}
|
|
return;
|
|
}
|
|
$stock = (int) get_post_meta($product_id, '_aksrt_stock', true);
|
|
if ($stock > 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): 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);
|
|
}
|
|
if ($type === 'invit_code') {
|
|
return $this->create_invite_codes($qty, $user_id, $order_number);
|
|
}
|
|
return '';
|
|
}
|
|
|
|
private function consume_card_keys(int $product_id, string $options_key, int $qty, int $user_id, string $order_number): string {
|
|
global $wpdb;
|
|
$table = AKSRT_DB_Migrator::table('card_key');
|
|
if ($table === '') 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",
|
|
...$params
|
|
), ARRAY_A);
|
|
|
|
if (empty($keys)) {
|
|
return __('卡密已售罄', 'aksrt');
|
|
}
|
|
|
|
$codes = [];
|
|
$ids = [];
|
|
foreach ($keys as $k) {
|
|
$codes[] = $k['card_code'];
|
|
$ids[] = (int) $k['id'];
|
|
}
|
|
if (!empty($ids)) {
|
|
$placeholders = implode(',', array_fill(0, count($ids), '%d'));
|
|
$wpdb->query($wpdb->prepare(
|
|
"UPDATE {$table} SET status = 'used', order_number = %s, used_by = %d, used_at = %s WHERE id IN ({$placeholders})",
|
|
array_merge([$order_number, $user_id, current_time('mysql')], $ids)
|
|
));
|
|
}
|
|
return implode("\n", $codes);
|
|
}
|
|
|
|
private function create_invite_codes(int $qty, int $user_id, string $order_number): string {
|
|
if (!class_exists('AKSRT_Invite')) {
|
|
return __('邀请系统不可用', 'aksrt');
|
|
}
|
|
$invite = AKSRT_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_DB_Migrator::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 = [];
|
|
|
|
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);
|
|
if ($content !== '') {
|
|
$auto_delivery_parts[] = $content;
|
|
$this->update_order_item_delivery($item['id'], $content);
|
|
}
|
|
}
|
|
}
|
|
|
|
$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);
|
|
}
|
|
|
|
do_action('aksrt_shop_fulfillment_done', $order_number, $order, $items);
|
|
}
|
|
|
|
private function update_order_item_delivery(int $item_id, string $content): void {
|
|
global $wpdb;
|
|
$table = $this->order_item_table();
|
|
if ($table === '') return;
|
|
$wpdb->update($table, ['auto_delivery_content' => $content], ['id' => $item_id], ['%s'], ['%d']);
|
|
}
|
|
|
|
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_Invite')) {
|
|
$referrer_id = AKSRT_Invite::get_instance()->get_referrer($user_id);
|
|
}
|
|
if ($referrer_id <= 0) return;
|
|
|
|
$rebate_table = AKSRT_DB_Migrator::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_Term_Meta')) {
|
|
$resolved = AKSRT_Shop_Term_Meta::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;
|
|
|
|
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') && (int) $gift_value > 0) {
|
|
AKSRT_Credits::add((int) $gift_value, $user_id, 'shop_gift', sprintf(__('订单 %s 赠品', 'aksrt'), $order_number));
|
|
} 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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|