refactor(autoload): migrate theme classes to Composer PSR-4
Move 288 types from global AKSRT_* + inc/classes manual requires into
namespace AKSRT\{Domain} under src/AKSRT/, wire composer.json psr-4,
and slim bootstrap to helpers/modules/hooks only. Split multi-class
widget/gateway files, update admin contract audit paths, and document
the layout in ADR/README.
This commit is contained in:
1 parent
8f349b40fb
commit
aaf458af1c
404 files changed
+13602
-7705
No files matched your search
@@ -0,0 +1,352 @@
|
||||
<?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): 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\Core\DBMigrator::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\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 = [];
|
||||
|
||||
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\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('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;
|
||||
|
||||
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 ($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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user