Files
wordpress-theme-aksrt/src/AKSRT/Shop/Part3.php
T

400 lines
17 KiB
PHP

<?php
/**
* @package AKSRT
*/
namespace AKSRT\Shop;
defined('ABSPATH') || exit;
trait Part3 {
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, 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');
}
}