feat(orders): order detail modal, list actions, use configured prefix
Wire admin payment_order_prefix into order number generation (with 32-char cap for gateways), make 查看 open a full order-detail modal with status-aware footer actions, keep type badge top-left only, and right-align unpaid list actions with countdown on the left.
This commit is contained in:
@@ -41,7 +41,7 @@ trait OrderRepositoryCore {
|
||||
$note = sanitize_textarea_field($note_raw);
|
||||
}
|
||||
|
||||
$wpdb->insert($this->table(), [
|
||||
$row = [
|
||||
'order_number' => $order_number,
|
||||
'user_id' => (int) ($data['user_id'] ?? 0),
|
||||
'product_type' => sanitize_key((string) ($data['product_type'] ?? '')),
|
||||
@@ -53,9 +53,17 @@ trait OrderRepositoryCore {
|
||||
'payment_transaction_id' => sanitize_text_field((string) ($data['payment_transaction_id'] ?? '')),
|
||||
'created_at' => $data['created_at'] ?? current_time('mysql'),
|
||||
'paid_at' => $data['paid_at'] ?? null,
|
||||
'expires_at' => $data['expires_at'] ?? null,
|
||||
'note' => $note,
|
||||
], ['%s', '%d', '%s', '%d', '%f', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s']);
|
||||
];
|
||||
$formats = ['%s', '%d', '%s', '%d', '%f', '%s', '%s', '%s', '%s', '%s', '%s', '%s'];
|
||||
|
||||
// Honour configured expiry when callers pass expires_at (shop checkout etc.).
|
||||
if (array_key_exists('expires_at', $data) && $data['expires_at'] !== null && $data['expires_at'] !== '') {
|
||||
$row['expires_at'] = (string) $data['expires_at'];
|
||||
$formats[] = '%s';
|
||||
}
|
||||
|
||||
$wpdb->insert($this->table(), $row, $formats);
|
||||
|
||||
if ($wpdb->insert_id) {
|
||||
return $order_number;
|
||||
@@ -506,8 +514,36 @@ trait OrderRepositoryCore {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Build a unique order number using the admin-configured prefix.
|
||||
*
|
||||
* Column + major gateways (WeChat out_trade_no) cap at 32 chars, so the
|
||||
* prefix is trimmed to leave room for {YmdHis}{4 digits} (=18).
|
||||
*/
|
||||
private function generate_order_number(): string {
|
||||
return 'AKS' . date('YmdHis') . str_pad((string) random_int(0, 9999), 4, '0', STR_PAD_LEFT);
|
||||
$prefix = 'AKSRT';
|
||||
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_order_settings')) {
|
||||
$settings = \AKSRT\Settings\Settings::get_order_settings();
|
||||
$configured = sanitize_text_field((string) ($settings['prefix'] ?? ''));
|
||||
if ($configured !== '') {
|
||||
$prefix = $configured;
|
||||
}
|
||||
}
|
||||
|
||||
// Keep only gateway-safe chars (REST + WeChat/Alipay out_trade_no).
|
||||
$prefix = preg_replace('/[^A-Za-z0-9_-]/', '', $prefix);
|
||||
if (!is_string($prefix) || $prefix === '') {
|
||||
$prefix = 'AKSRT';
|
||||
}
|
||||
|
||||
$body = date('YmdHis') . str_pad((string) random_int(0, 9999), 4, '0', STR_PAD_LEFT);
|
||||
// Reserve 18 for body; leave ≥1 char of prefix so numbers stay human-readable.
|
||||
$max_prefix = max(1, 32 - strlen($body));
|
||||
if (strlen($prefix) > $max_prefix) {
|
||||
$prefix = substr($prefix, 0, $max_prefix);
|
||||
}
|
||||
|
||||
return $prefix . $body;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -185,7 +185,9 @@ class UserOrders {
|
||||
$can_receipt = $is_shop && $status === Order::STATUS_DELIVERED;
|
||||
$can_review = $is_shop && $status === Order::STATUS_PENDING_REVIEW;
|
||||
$can_rebuy = $is_shop && in_array($status, [Order::STATUS_COMPLETED, Order::STATUS_PENDING_REVIEW], true);
|
||||
$can_view = $permalink !== '' && in_array($status, [
|
||||
// Order detail modal is always available; product permalink is optional.
|
||||
$can_view = true;
|
||||
$can_view_product = $permalink !== '' && in_array($status, [
|
||||
Order::STATUS_PAID,
|
||||
Order::STATUS_DELIVERED,
|
||||
Order::STATUS_COMPLETED,
|
||||
@@ -232,14 +234,16 @@ class UserOrders {
|
||||
'shipping_records' => $shipping_records,
|
||||
'pay_url' => '#',
|
||||
'actions' => [
|
||||
'can_pay' => $can_pay,
|
||||
'can_cancel' => $can_cancel,
|
||||
'can_after_sale' => $can_after_sale,
|
||||
'can_receipt' => $can_receipt,
|
||||
'can_review' => $can_review,
|
||||
'can_rebuy' => $can_rebuy,
|
||||
'can_view' => $can_view,
|
||||
'can_pay' => $can_pay,
|
||||
'can_cancel' => $can_cancel,
|
||||
'can_after_sale' => $can_after_sale,
|
||||
'can_receipt' => $can_receipt,
|
||||
'can_review' => $can_review,
|
||||
'can_rebuy' => $can_rebuy,
|
||||
'can_view' => $can_view,
|
||||
'can_view_product' => $can_view_product,
|
||||
],
|
||||
'quantity' => $this->resolve_order_quantity($order_items, $meta),
|
||||
'time_left' => $this->get_order_countdown($expires_at, $status),
|
||||
'secondary_url' => $this->resolve_secondary_url($product_type),
|
||||
'secondary_label' => $this->resolve_secondary_label($product_type),
|
||||
@@ -534,4 +538,25 @@ class UserOrders {
|
||||
$left = strtotime($expires_at) - time();
|
||||
return max(0, (int) $left);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sum line quantities for display (shop multi-item or single meta item).
|
||||
*
|
||||
* @param array<int, array<string, mixed>> $order_items
|
||||
* @param array<string, mixed> $meta
|
||||
*/
|
||||
private function resolve_order_quantity(array $order_items, array $meta): int {
|
||||
$qty = 0;
|
||||
$source = !empty($order_items) ? $order_items : (is_array($meta['items'] ?? null) ? $meta['items'] : []);
|
||||
foreach ($source as $row) {
|
||||
if (!is_array($row)) {
|
||||
continue;
|
||||
}
|
||||
$qty += max(1, (int) ($row['quantity'] ?? $row['qty'] ?? 1));
|
||||
}
|
||||
if ($qty <= 0) {
|
||||
$qty = max(1, (int) ($meta['quantity'] ?? $meta['qty'] ?? 1));
|
||||
}
|
||||
return $qty;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user