Merge pull request 'feat(orders): full-chain My Orders for all product types' (#291) from worktree-feat-my-orders into main
Reviewed-on: CYL/wordpress-theme-aksrt#291
This commit is contained in:
@@ -436,3 +436,65 @@ html.dark .aksrt-orders-page__filter-chip.is-active,
|
||||
html.dark .aksrt-orders-page__filter-chip.is-active {
|
||||
border-color: color-mix(in srgb, var(--color-primary) 45%, var(--color-line, #e5e7eb));
|
||||
}
|
||||
|
||||
/* ===== Stats + type filters (My Orders) ===== */
|
||||
.aksrt-orders-page__stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
|
||||
.aksrt-orders-page__stat {
|
||||
padding: 14px 16px;
|
||||
border-radius: var(--radius-md, 12px);
|
||||
border: 1px solid var(--color-line);
|
||||
background: var(--surface-4, #fff);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.aksrt-orders-page__stat-label {
|
||||
font-size: 12px;
|
||||
color: var(--color-ink-3, #8b8f97);
|
||||
}
|
||||
|
||||
.aksrt-orders-page__stat-value {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: var(--color-ink, #1f2329);
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.aksrt-orders-page__type-filters {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 0 14px;
|
||||
}
|
||||
|
||||
.aksrt-orders-page__card-type {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--color-primary, #3b82f6) 12%, transparent);
|
||||
color: var(--color-primary, #3b82f6);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.aksrt-orders-page__card-head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.aksrt-orders-page__stats {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,21 +52,38 @@ function aksrt_get_cart_page_view(int $user_id): array {
|
||||
return \AKSRT\Shop\Shop::get_instance()->get_cart_page_view($user_id);
|
||||
}
|
||||
|
||||
function aksrt_get_orders_page_view(int $user_id, int $paged = 1, int $per_page = 20, string $status = ''): array {
|
||||
if ($user_id <= 0 || !class_exists('\AKSRT\Shop\Shop')) {
|
||||
return [
|
||||
'items' => [],
|
||||
'total' => 0,
|
||||
'total_pages' => 1,
|
||||
'page' => 1,
|
||||
];
|
||||
function aksrt_get_orders_page_view(int $user_id, int $paged = 1, int $per_page = 20, string $status = '', string $product_type = ''): array {
|
||||
$empty = [
|
||||
'items' => [],
|
||||
'total' => 0,
|
||||
'total_pages' => 1,
|
||||
'page' => 1,
|
||||
'stats' => ['total' => 0, 'pending' => 0, 'paid' => 0],
|
||||
'product_types' => class_exists('\AKSRT\Core\DomainModel')
|
||||
? array_merge(['' => __('全部类型', 'aksrt')], \AKSRT\Core\DomainModel::order_product_type_labels())
|
||||
: [],
|
||||
];
|
||||
|
||||
if ($user_id <= 0) {
|
||||
return $empty;
|
||||
}
|
||||
|
||||
return \AKSRT\Shop\Shop::get_instance()->get_order_center_view($user_id, [
|
||||
'page' => $paged,
|
||||
'per_page' => $per_page,
|
||||
'status' => $status,
|
||||
]);
|
||||
$args = [
|
||||
'page' => $paged,
|
||||
'per_page' => $per_page,
|
||||
'status' => $status,
|
||||
'product_type' => $product_type,
|
||||
];
|
||||
|
||||
if (class_exists('\AKSRT\Payment\UserOrders')) {
|
||||
return \AKSRT\Payment\UserOrders::get_instance()->get_order_center_view($user_id, $args);
|
||||
}
|
||||
|
||||
if (class_exists('\AKSRT\Shop\Shop')) {
|
||||
return \AKSRT\Shop\Shop::get_instance()->get_order_center_view($user_id, $args);
|
||||
}
|
||||
|
||||
return $empty;
|
||||
}
|
||||
|
||||
function aksrt_get_revenue_page_view(int $user_id): array {
|
||||
|
||||
@@ -74,12 +74,16 @@ endif;
|
||||
if (!function_exists('aksrt_get_orders_page_url')) :
|
||||
/**
|
||||
* 订单中心页面 URL(模板 templates/pages/page-orders.php)。
|
||||
* 未指派页面时返回空串,调用方应隐藏入口。
|
||||
* 未指派页面时回退 /orders/(与 Scripts 路由一致),保证「权益与消费」入口可用。
|
||||
*/
|
||||
function aksrt_get_orders_page_url(): string {
|
||||
return function_exists('aksrt_get_page_url_by_template')
|
||||
$url = function_exists('aksrt_get_page_url_by_template')
|
||||
? aksrt_get_page_url_by_template('templates/pages/page-orders.php')
|
||||
: '';
|
||||
if ($url !== '') {
|
||||
return $url;
|
||||
}
|
||||
return home_url('/orders/');
|
||||
}
|
||||
endif;
|
||||
|
||||
|
||||
@@ -43,30 +43,37 @@ trait ShopControllerFrontendFlowOperations {
|
||||
}
|
||||
|
||||
public function get_frontend_orders(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if (!class_exists('\AKSRT\Shop\Shop')) {
|
||||
return $this->success(['data' => ['items' => [], 'total' => 0, 'total_pages' => 1, 'page' => 1]]);
|
||||
}
|
||||
|
||||
$page = max(1, (int) $request->get_param('page'));
|
||||
$per_page = max(1, min(50, (int) $request->get_param('per_page'))) ?: 20;
|
||||
$status = sanitize_key((string) $request->get_param('status'));
|
||||
$product_type = sanitize_key((string) $request->get_param('product_type'));
|
||||
|
||||
$view = \AKSRT\Shop\Shop::get_instance()->get_order_center_view(get_current_user_id(), [
|
||||
'page' => $page,
|
||||
'per_page' => $per_page,
|
||||
'status' => $status,
|
||||
]);
|
||||
$args = [
|
||||
'page' => $page,
|
||||
'per_page' => $per_page,
|
||||
'status' => $status,
|
||||
'product_type' => $product_type,
|
||||
];
|
||||
|
||||
if (class_exists('\AKSRT\Payment\UserOrders')) {
|
||||
$view = \AKSRT\Payment\UserOrders::get_instance()->get_order_center_view(get_current_user_id(), $args);
|
||||
} elseif (class_exists('\AKSRT\Shop\Shop')) {
|
||||
$view = \AKSRT\Shop\Shop::get_instance()->get_order_center_view(get_current_user_id(), $args);
|
||||
} else {
|
||||
$view = ['items' => [], 'total' => 0, 'total_pages' => 1, 'page' => 1];
|
||||
}
|
||||
|
||||
return $this->success(['data' => $view]);
|
||||
}
|
||||
|
||||
public function get_frontend_order(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if (!class_exists('\AKSRT\Shop\Shop')) {
|
||||
return $this->error('商城服务不可用', 500);
|
||||
}
|
||||
|
||||
$order_number = sanitize_text_field((string) $request->get_param('order_number'));
|
||||
$order = \AKSRT\Shop\Shop::get_instance()->get_order_detail_view(get_current_user_id(), $order_number);
|
||||
$order = null;
|
||||
if (class_exists('\AKSRT\Payment\UserOrders')) {
|
||||
$order = \AKSRT\Payment\UserOrders::get_instance()->get_order_detail_view(get_current_user_id(), $order_number);
|
||||
} elseif (class_exists('\AKSRT\Shop\Shop')) {
|
||||
$order = \AKSRT\Shop\Shop::get_instance()->get_order_detail_view(get_current_user_id(), $order_number);
|
||||
}
|
||||
if (!$order) {
|
||||
return $this->error('订单不存在', 404);
|
||||
}
|
||||
@@ -75,15 +82,17 @@ trait ShopControllerFrontendFlowOperations {
|
||||
}
|
||||
|
||||
public function pay_frontend_order(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if (!class_exists('\AKSRT\Shop\Shop')) {
|
||||
return $this->error('商城服务不可用', 500);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$payment_method = sanitize_key((string) ($params['payment_method'] ?? 'balance'));
|
||||
$payment_method = sanitize_key((string) ($params['payment_method'] ?? ''));
|
||||
$order_number = sanitize_text_field((string) $request->get_param('order_number'));
|
||||
|
||||
$result = \AKSRT\Shop\Shop::get_instance()->pay_order(get_current_user_id(), $order_number, $payment_method);
|
||||
if (class_exists('\AKSRT\Payment\UserOrders')) {
|
||||
$result = \AKSRT\Payment\UserOrders::get_instance()->pay_order(get_current_user_id(), $order_number, $payment_method);
|
||||
} elseif (class_exists('\AKSRT\Shop\Shop')) {
|
||||
$result = \AKSRT\Shop\Shop::get_instance()->pay_order(get_current_user_id(), $order_number, $payment_method ?: 'balance');
|
||||
} else {
|
||||
return $this->error('订单服务不可用', 500);
|
||||
}
|
||||
if (empty($result['success'])) {
|
||||
return $this->error($result['message'] ?? '支付失败');
|
||||
}
|
||||
@@ -92,12 +101,14 @@ trait ShopControllerFrontendFlowOperations {
|
||||
}
|
||||
|
||||
public function cancel_frontend_order(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if (!class_exists('\AKSRT\Shop\Shop')) {
|
||||
return $this->error('商城服务不可用', 500);
|
||||
}
|
||||
|
||||
$order_number = sanitize_text_field((string) $request->get_param('order_number'));
|
||||
$result = \AKSRT\Shop\Shop::get_instance()->cancel_order(get_current_user_id(), $order_number);
|
||||
if (class_exists('\AKSRT\Payment\UserOrders')) {
|
||||
$result = \AKSRT\Payment\UserOrders::get_instance()->cancel_order(get_current_user_id(), $order_number);
|
||||
} elseif (class_exists('\AKSRT\Shop\Shop')) {
|
||||
$result = \AKSRT\Shop\Shop::get_instance()->cancel_order(get_current_user_id(), $order_number);
|
||||
} else {
|
||||
return $this->error('订单服务不可用', 500);
|
||||
}
|
||||
if (empty($result['success'])) {
|
||||
return $this->error($result['message'] ?? '取消订单失败');
|
||||
}
|
||||
|
||||
@@ -34,6 +34,22 @@ class DomainModel {
|
||||
/** Paid plate follow (zibll order_type=11). product_id = plate post ID. */
|
||||
public const ORDER_PRODUCT_PLATE_FOLLOW = 'plate_follow';
|
||||
|
||||
/**
|
||||
* Human labels for aksrt_order.product_type (front-end order center).
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function order_product_type_labels(): array {
|
||||
return [
|
||||
self::ORDER_PRODUCT_SHOP => __('商城商品', 'aksrt'),
|
||||
self::ORDER_PRODUCT_RESOURCE => __('付费内容', 'aksrt'),
|
||||
self::ORDER_PRODUCT_VIP => __('VIP 会员', 'aksrt'),
|
||||
self::ORDER_PRODUCT_RECHARGE => __('余额充值', 'aksrt'),
|
||||
self::ORDER_PRODUCT_POINTS_BUY => __('购买积分', 'aksrt'),
|
||||
self::ORDER_PRODUCT_PLATE_FOLLOW => __('关注版块', 'aksrt'),
|
||||
];
|
||||
}
|
||||
|
||||
public const PAY_TYPE_NO = 'no';
|
||||
public const PAY_TYPE_READING = 'reading';
|
||||
public const PAY_TYPE_DOWNLOAD = 'download';
|
||||
|
||||
@@ -0,0 +1,528 @@
|
||||
<?php
|
||||
/**
|
||||
* Front-end "My Orders" view-model (all product_types).
|
||||
*
|
||||
* Shop logistics/review stay shop-only; content/VIP/recharge get type-aware cards.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Payment;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class UserOrders {
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginated user order center payload.
|
||||
*
|
||||
* @param array{page?:int,per_page?:int,status?:string,product_type?:string} $args
|
||||
* @return array{items:array,total:int,total_pages:int,page:int,stats:array,product_types:array}
|
||||
*/
|
||||
public function get_order_center_view(int $user_id, array $args = []): array {
|
||||
if ($user_id <= 0 || !class_exists(OrderRepository::class)) {
|
||||
return [
|
||||
'items' => [],
|
||||
'total' => 0,
|
||||
'total_pages' => 1,
|
||||
'page' => 1,
|
||||
'stats' => $this->empty_stats(),
|
||||
'product_types' => $this->product_type_filter_options(),
|
||||
];
|
||||
}
|
||||
|
||||
$page = max(1, (int) ($args['page'] ?? 1));
|
||||
$per_page = max(1, min(50, (int) ($args['per_page'] ?? 20)));
|
||||
$status = sanitize_key((string) ($args['status'] ?? ''));
|
||||
$product_type = sanitize_key((string) ($args['product_type'] ?? ''));
|
||||
|
||||
$repo_args = [
|
||||
'limit' => $per_page,
|
||||
'offset' => ($page - 1) * $per_page,
|
||||
];
|
||||
if ($status !== '') {
|
||||
$repo_args['status'] = $status;
|
||||
}
|
||||
if ($product_type !== '' && $product_type !== 'all') {
|
||||
$repo_args['product_type'] = $product_type;
|
||||
}
|
||||
|
||||
$repo = OrderRepository::get_instance();
|
||||
$orders = $repo->get_user_orders($user_id, $repo_args);
|
||||
$total = $repo->count_user_orders($user_id, $repo_args);
|
||||
|
||||
$items = [];
|
||||
foreach ($orders as $order) {
|
||||
$items[] = $this->decorate_order_view($order, true);
|
||||
}
|
||||
|
||||
return [
|
||||
'items' => $items,
|
||||
'total' => $total,
|
||||
'total_pages' => max(1, (int) ceil(max(0, $total) / $per_page)),
|
||||
'page' => $page,
|
||||
'stats' => $this->get_user_order_stats($user_id),
|
||||
'product_types' => $this->product_type_filter_options(),
|
||||
];
|
||||
}
|
||||
|
||||
public function get_order_detail_view(int $user_id, string $order_number): ?array {
|
||||
if ($user_id <= 0 || $order_number === '' || !class_exists(OrderRepository::class)) {
|
||||
return null;
|
||||
}
|
||||
$order = OrderRepository::get_instance()->get_user_order($user_id, $order_number);
|
||||
if (!$order) {
|
||||
return null;
|
||||
}
|
||||
return $this->decorate_order_view($order, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{total:int,pending:int,paid:int}
|
||||
*/
|
||||
public function get_user_order_stats(int $user_id): array {
|
||||
if ($user_id <= 0 || !class_exists(OrderRepository::class)) {
|
||||
return $this->empty_stats();
|
||||
}
|
||||
$repo = OrderRepository::get_instance();
|
||||
return [
|
||||
'total' => $repo->count_user_orders($user_id, []),
|
||||
'pending' => $repo->count_user_orders($user_id, ['status' => Order::STATUS_PENDING]),
|
||||
'paid' => $repo->count_user_orders($user_id, ['status' => Order::STATUS_PAID]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function product_type_filter_options(): array {
|
||||
$labels = \AKSRT\Core\DomainModel::order_product_type_labels();
|
||||
return array_merge(['' => __('全部类型', 'aksrt')], $labels);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $order Normalized order row.
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function decorate_order_view(array $order, bool $with_detail = false): array {
|
||||
$meta = is_array($order['meta_payload'] ?? null) ? $order['meta_payload'] : [];
|
||||
$order_number = (string) ($order['order_number'] ?? '');
|
||||
$product_type = sanitize_key((string) ($order['product_type'] ?? ''));
|
||||
$status = (string) ($order['status'] ?? Order::STATUS_PENDING);
|
||||
$product_id = (int) ($order['product_id'] ?? 0);
|
||||
$is_shop = $product_type === \AKSRT\Core\DomainModel::ORDER_PRODUCT_SHOP
|
||||
|| $product_type === 'shop';
|
||||
$currency = sanitize_key((string) ($order['currency'] ?? 'CNY'));
|
||||
$is_points = $currency === 'points'
|
||||
|| sanitize_key((string) ($order['payment_method'] ?? '')) === 'points'
|
||||
|| (!empty($meta['pay_modo']) && (string) $meta['pay_modo'] === 'points');
|
||||
|
||||
$type_labels = \AKSRT\Core\DomainModel::order_product_type_labels();
|
||||
$type_label = $type_labels[$product_type] ?? ($product_type !== '' ? $product_type : __('订单', 'aksrt'));
|
||||
|
||||
$title = $this->resolve_order_title($order, $meta, $product_type, $product_id, $type_label);
|
||||
$image = $this->resolve_order_image($order, $meta, $product_type, $product_id);
|
||||
$permalink = $this->resolve_order_permalink($product_type, $product_id, $meta);
|
||||
|
||||
$order_items = [];
|
||||
if ($is_shop && class_exists('\AKSRT\Shop\Shop')) {
|
||||
$order_items = \AKSRT\Shop\Shop::get_instance()->get_order_items($order_number);
|
||||
}
|
||||
$has_structured_items = !empty($order_items);
|
||||
|
||||
$product = [];
|
||||
if ($has_structured_items && class_exists('\AKSRT\Shop\Shop')) {
|
||||
$product = \AKSRT\Shop\Shop::get_instance()->build_product_card_view((int) ($order_items[0]['product_id'] ?? 0));
|
||||
} elseif ($is_shop && $product_id > 0 && class_exists('\AKSRT\Shop\Shop')) {
|
||||
$product = \AKSRT\Shop\Shop::get_instance()->build_product_card_view($product_id);
|
||||
}
|
||||
|
||||
if (!empty($product['title'])) {
|
||||
$title = (string) $product['title'];
|
||||
}
|
||||
if (!empty($product['image'])) {
|
||||
$image = (string) $product['image'];
|
||||
}
|
||||
if (!empty($product['permalink'])) {
|
||||
$permalink = (string) $product['permalink'];
|
||||
}
|
||||
|
||||
$shipping_records = [];
|
||||
$after_sale_items = [];
|
||||
if ($is_shop) {
|
||||
if (class_exists(Shipping::class)) {
|
||||
$shipping_records = Shipping::get_instance()->get_by_order($order_number);
|
||||
}
|
||||
if (class_exists(AfterSale::class)) {
|
||||
$as = AfterSale::get_instance()->get_list([
|
||||
'limit' => 20,
|
||||
'offset' => 0,
|
||||
'order_number' => $order_number,
|
||||
]);
|
||||
$after_sale_items = $as['items'] ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
$subtotal = (float) ($meta['items'][0]['line_total'] ?? $order['amount'] ?? 0);
|
||||
$shipping_fee = (float) ($meta['shipping_fee'] ?? 0);
|
||||
$discount = (float) ($meta['discount'] ?? 0);
|
||||
$expires_at = (string) ($order['expires_at'] ?? '');
|
||||
$payment_value = sanitize_key((string) ($meta['payment_method']['value'] ?? $order['payment_method'] ?? 'balance'));
|
||||
|
||||
$can_pay = in_array($status, [Order::STATUS_PENDING, Order::STATUS_EXPIRED, Order::STATUS_CREATED], true)
|
||||
&& $payment_value !== 'manual_review';
|
||||
$can_cancel = in_array($status, [Order::STATUS_PENDING, Order::STATUS_CREATED, Order::STATUS_EXPIRED], true);
|
||||
$can_after_sale = $is_shop && in_array($status, [Order::STATUS_PAID, Order::STATUS_DELIVERED, Order::STATUS_COMPLETED], true);
|
||||
$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::STATUS_PAID,
|
||||
Order::STATUS_DELIVERED,
|
||||
Order::STATUS_COMPLETED,
|
||||
Order::STATUS_PENDING_REVIEW,
|
||||
], true);
|
||||
|
||||
$view = [
|
||||
'id' => (int) ($order['id'] ?? 0),
|
||||
'order_number' => $order_number,
|
||||
'amount' => (float) ($order['amount'] ?? 0),
|
||||
'currency' => $currency,
|
||||
'currency_symbol' => $is_points
|
||||
? (class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_currency_name() : __('积分', 'aksrt'))
|
||||
: '¥',
|
||||
'is_points' => $is_points,
|
||||
'status' => $status,
|
||||
'status_label' => Order::get_valid_statuses()[$status] ?? $status,
|
||||
'product_type' => $product_type,
|
||||
'product_type_label' => $type_label,
|
||||
'product_id' => $product_id,
|
||||
'product_name' => $title,
|
||||
'product' => $product ?: [
|
||||
'title' => $title,
|
||||
'image' => $image,
|
||||
'permalink' => $permalink,
|
||||
],
|
||||
'image' => $image,
|
||||
'permalink' => $permalink,
|
||||
'created_at' => (string) ($order['created_at'] ?? ''),
|
||||
'paid_at' => (string) ($order['paid_at'] ?? ''),
|
||||
'expires_at' => $expires_at,
|
||||
'subtotal' => $subtotal,
|
||||
'shipping_fee' => $shipping_fee,
|
||||
'discount' => $discount,
|
||||
'address' => $meta['address'] ?? [],
|
||||
'payment_method' => $meta['payment_method'] ?? [
|
||||
'value' => (string) ($order['payment_method'] ?? ''),
|
||||
'label' => $this->payment_method_label((string) ($order['payment_method'] ?? '')),
|
||||
],
|
||||
'coupon' => $meta['coupon'] ?? [],
|
||||
'items' => $has_structured_items ? $order_items : ($meta['items'] ?? []),
|
||||
'shipping_method' => $meta['shipping_method'] ?? [],
|
||||
'customer_note' => $meta['customer_note'] ?? ($order['note_text'] ?? ''),
|
||||
'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,
|
||||
],
|
||||
'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),
|
||||
];
|
||||
|
||||
if ($with_detail) {
|
||||
$view['after_sale_records'] = $after_sale_items;
|
||||
$view['timeline'] = $this->build_order_timeline($order, $shipping_records, $after_sale_items);
|
||||
}
|
||||
|
||||
return $view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel pending order (any product_type).
|
||||
*
|
||||
* @return array{success:bool,message?:string,data?:array}
|
||||
*/
|
||||
public function cancel_order(int $user_id, string $order_number): array {
|
||||
if ($user_id <= 0 || $order_number === '' || !class_exists(OrderRepository::class)) {
|
||||
return ['success' => false, 'message' => __('订单不存在。', 'aksrt')];
|
||||
}
|
||||
|
||||
$repo = OrderRepository::get_instance();
|
||||
$order = $repo->get_user_order($user_id, $order_number);
|
||||
if (!$order) {
|
||||
return ['success' => false, 'message' => __('订单不存在。', 'aksrt')];
|
||||
}
|
||||
|
||||
$status = (string) ($order['status'] ?? '');
|
||||
if (!in_array($status, [Order::STATUS_PENDING, Order::STATUS_CREATED, Order::STATUS_EXPIRED], true)) {
|
||||
return ['success' => false, 'message' => __('当前订单不可取消。', 'aksrt')];
|
||||
}
|
||||
|
||||
$repo->update_status($order_number, Order::STATUS_CANCELLED, [
|
||||
'force' => true,
|
||||
'note' => __('用户在前台取消订单。', 'aksrt'),
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'order' => $this->get_order_detail_view($user_id, $order_number),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume payment via configured gateway (any product_type).
|
||||
*
|
||||
* @return array{success:bool,message?:string,data?:array}
|
||||
*/
|
||||
public function pay_order(int $user_id, string $order_number, string $payment_method = ''): array {
|
||||
if ($user_id <= 0 || $order_number === '' || !class_exists(OrderRepository::class)) {
|
||||
return ['success' => false, 'message' => __('订单不存在。', 'aksrt')];
|
||||
}
|
||||
|
||||
// Prefer shop checkout when available (handles points + shop meta).
|
||||
if (class_exists('\AKSRT\Shop\Shop')) {
|
||||
return \AKSRT\Shop\Shop::get_instance()->pay_order($user_id, $order_number, $payment_method);
|
||||
}
|
||||
|
||||
$repo = OrderRepository::get_instance();
|
||||
$order = $repo->get_user_order($user_id, $order_number);
|
||||
if (!$order) {
|
||||
return ['success' => false, 'message' => __('订单不存在。', 'aksrt')];
|
||||
}
|
||||
|
||||
$payment_method = sanitize_key($payment_method ?: (string) ($order['payment_method'] ?? 'balance'));
|
||||
$gateway_manager = class_exists(Gateway::class) ? Gateway::get_instance() : null;
|
||||
$gateway = $gateway_manager ? $gateway_manager->get_gateway($payment_method) : null;
|
||||
if (!$gateway) {
|
||||
return ['success' => false, 'message' => __('当前支付方式不可用。', 'aksrt')];
|
||||
}
|
||||
if (method_exists($gateway, 'is_configured') && !$gateway->is_configured()) {
|
||||
return ['success' => false, 'message' => __('该支付方式尚未配置,请联系站长。', 'aksrt')];
|
||||
}
|
||||
|
||||
$order['subject'] = $order['subject'] ?? sprintf(__('订单 %s', 'aksrt'), $order_number);
|
||||
$payment_result = $gateway->create_payment($order);
|
||||
if (empty($payment_result['success'])) {
|
||||
return ['success' => false, 'message' => $payment_result['message'] ?? __('支付失败。', 'aksrt')];
|
||||
}
|
||||
|
||||
$code_url = $payment_result['payment_data']['code_url'] ?? '';
|
||||
if (!empty($payment_result['payment_url']) || $code_url !== '') {
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'redirect' => true,
|
||||
'payment_url' => (string) ($payment_result['payment_url'] ?? ''),
|
||||
'qr_content' => (string) $code_url,
|
||||
'order_number' => $order_number,
|
||||
'message' => $payment_result['message'] ?? __('请在新页面完成支付。', 'aksrt'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$repo->update_status($order_number, Order::STATUS_PAID, [
|
||||
'payment_method' => $payment_method,
|
||||
'payment_transaction_id' => sanitize_text_field((string) ($payment_result['transaction_id'] ?? '')),
|
||||
'note' => __('用户在前台完成支付。', 'aksrt'),
|
||||
]);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'order' => $this->get_order_detail_view($user_id, $order_number),
|
||||
'message' => $payment_result['message'] ?? __('支付成功。', 'aksrt'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function empty_stats(): array {
|
||||
return ['total' => 0, 'pending' => 0, 'paid' => 0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $order
|
||||
* @param array<string, mixed> $meta
|
||||
*/
|
||||
private function resolve_order_title(array $order, array $meta, string $product_type, int $product_id, string $type_label): string {
|
||||
foreach (['product_title', 'title', 'package_title'] as $key) {
|
||||
if (!empty($meta[$key]) && is_string($meta[$key])) {
|
||||
return (string) $meta[$key];
|
||||
}
|
||||
}
|
||||
|
||||
if ($product_type === \AKSRT\Core\DomainModel::ORDER_PRODUCT_VIP) {
|
||||
$pkg = (string) ($meta['package_title'] ?? '');
|
||||
if ($pkg !== '') {
|
||||
return $pkg;
|
||||
}
|
||||
return __('VIP 会员', 'aksrt');
|
||||
}
|
||||
|
||||
if ($product_type === \AKSRT\Core\DomainModel::ORDER_PRODUCT_RECHARGE) {
|
||||
return (string) ($meta['title'] ?? __('余额充值', 'aksrt'));
|
||||
}
|
||||
|
||||
if ($product_type === \AKSRT\Core\DomainModel::ORDER_PRODUCT_POINTS_BUY) {
|
||||
return (string) ($meta['title'] ?? __('购买积分', 'aksrt'));
|
||||
}
|
||||
|
||||
if ($product_id > 0) {
|
||||
$post_title = get_the_title($product_id);
|
||||
if (is_string($post_title) && $post_title !== '') {
|
||||
return $post_title;
|
||||
}
|
||||
}
|
||||
|
||||
return $type_label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $order
|
||||
* @param array<string, mixed> $meta
|
||||
*/
|
||||
private function resolve_order_image(array $order, array $meta, string $product_type, int $product_id): string {
|
||||
if (!empty($meta['thumb']) && is_string($meta['thumb'])) {
|
||||
return esc_url_raw((string) $meta['thumb']);
|
||||
}
|
||||
if ($product_id > 0 && has_post_thumbnail($product_id)) {
|
||||
$url = get_the_post_thumbnail_url($product_id, 'thumbnail');
|
||||
if (is_string($url) && $url !== '') {
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $meta
|
||||
*/
|
||||
private function resolve_order_permalink(string $product_type, int $product_id, array $meta): string {
|
||||
if ($product_type === \AKSRT\Core\DomainModel::ORDER_PRODUCT_VIP) {
|
||||
return function_exists('aksrt_get_user_center_tab_url')
|
||||
? aksrt_get_user_center_tab_url('vip')
|
||||
: '';
|
||||
}
|
||||
if ($product_type === \AKSRT\Core\DomainModel::ORDER_PRODUCT_RECHARGE
|
||||
|| $product_type === \AKSRT\Core\DomainModel::ORDER_PRODUCT_POINTS_BUY) {
|
||||
return function_exists('aksrt_get_user_center_tab_url')
|
||||
? aksrt_get_user_center_tab_url('credits')
|
||||
: '';
|
||||
}
|
||||
$post_id = $product_id > 0 ? $product_id : (int) ($meta['post_id'] ?? 0);
|
||||
if ($post_id > 0) {
|
||||
$link = get_permalink($post_id);
|
||||
return is_string($link) ? $link : '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
private function resolve_secondary_url(string $product_type): string {
|
||||
if ($product_type === \AKSRT\Core\DomainModel::ORDER_PRODUCT_VIP) {
|
||||
return function_exists('aksrt_get_user_center_tab_url') ? aksrt_get_user_center_tab_url('vip') : '';
|
||||
}
|
||||
if (in_array($product_type, [
|
||||
\AKSRT\Core\DomainModel::ORDER_PRODUCT_RECHARGE,
|
||||
\AKSRT\Core\DomainModel::ORDER_PRODUCT_POINTS_BUY,
|
||||
], true)) {
|
||||
return function_exists('aksrt_get_user_center_tab_url') ? aksrt_get_user_center_tab_url('credits') : '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
private function resolve_secondary_label(string $product_type): string {
|
||||
if ($product_type === \AKSRT\Core\DomainModel::ORDER_PRODUCT_VIP) {
|
||||
return __('我的会员', 'aksrt');
|
||||
}
|
||||
if ($product_type === \AKSRT\Core\DomainModel::ORDER_PRODUCT_RECHARGE) {
|
||||
return __('我的余额', 'aksrt');
|
||||
}
|
||||
if ($product_type === \AKSRT\Core\DomainModel::ORDER_PRODUCT_POINTS_BUY) {
|
||||
return class_exists('\AKSRT\Settings\Settings')
|
||||
? sprintf(__('我的%s', 'aksrt'), \AKSRT\Settings\Settings::get_currency_name())
|
||||
: __('我的积分', 'aksrt');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
private function payment_method_label(string $method): string {
|
||||
$method = sanitize_key($method);
|
||||
$map = [
|
||||
'balance' => __('站内余额', 'aksrt'),
|
||||
'points' => __('积分支付', 'aksrt'),
|
||||
'alipay' => __('支付宝', 'aksrt'),
|
||||
'wechatpay' => __('微信支付', 'aksrt'),
|
||||
'wechat' => __('微信支付', 'aksrt'),
|
||||
'paypal' => 'PayPal',
|
||||
'stripe' => 'Stripe',
|
||||
];
|
||||
return $map[$method] ?? ($method !== '' ? $method : __('—', 'aksrt'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $order
|
||||
* @param array<int, array> $shipping_records
|
||||
* @param array<int, array> $after_sale_records
|
||||
* @return array<int, array{time:string,label:string}>
|
||||
*/
|
||||
private function build_order_timeline(array $order, array $shipping_records, array $after_sale_records): array {
|
||||
$timeline = [];
|
||||
$date_fields = [
|
||||
'created_at' => __('订单创建', 'aksrt'),
|
||||
'paid_at' => __('完成支付', 'aksrt'),
|
||||
'delivered_at' => __('订单发货', 'aksrt'),
|
||||
'completed_at' => __('交易完成', 'aksrt'),
|
||||
'cancelled_at' => __('订单取消', 'aksrt'),
|
||||
'refunded_at' => __('完成退款', 'aksrt'),
|
||||
];
|
||||
foreach ($date_fields as $key => $label) {
|
||||
if (!empty($order[$key])) {
|
||||
$timeline[] = [
|
||||
'time' => (string) $order[$key],
|
||||
'label' => $label,
|
||||
];
|
||||
}
|
||||
}
|
||||
foreach ($shipping_records as $record) {
|
||||
$timeline[] = [
|
||||
'time' => (string) ($record['created_at'] ?? ''),
|
||||
'label' => sprintf(__('物流更新:%s', 'aksrt'), $record['tracking_no'] ?? ''),
|
||||
];
|
||||
}
|
||||
foreach ($after_sale_records as $record) {
|
||||
$timeline[] = [
|
||||
'time' => (string) ($record['created_at'] ?? ''),
|
||||
'label' => sprintf(__('售后申请:%s', 'aksrt'), $record['reason'] ?? ''),
|
||||
];
|
||||
}
|
||||
usort($timeline, static function ($a, $b) {
|
||||
return strcmp($a['time'] ?? '', $b['time'] ?? '');
|
||||
});
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
private function get_order_countdown(string $expires_at, string $status): int {
|
||||
if ($expires_at === '' || !in_array($status, [Order::STATUS_PENDING, Order::STATUS_EXPIRED, Order::STATUS_CREATED], true)) {
|
||||
return 0;
|
||||
}
|
||||
$left = strtotime($expires_at) - time();
|
||||
return max(0, (int) $left);
|
||||
}
|
||||
}
|
||||
+6
-38
@@ -266,51 +266,19 @@ class Shop {
|
||||
}
|
||||
|
||||
public function get_order_center_view(int $user_id, array $args = []): array {
|
||||
if ($user_id <= 0 || !class_exists('\AKSRT\Payment\OrderRepository')) {
|
||||
return ['items' => [], 'total' => 0, 'total_pages' => 1, 'page' => 1];
|
||||
if (class_exists('\AKSRT\Payment\UserOrders')) {
|
||||
return \AKSRT\Payment\UserOrders::get_instance()->get_order_center_view($user_id, $args);
|
||||
}
|
||||
|
||||
$page = max(1, (int) ($args['page'] ?? 1));
|
||||
$per_page = max(1, min(50, (int) ($args['per_page'] ?? 20)));
|
||||
$status = sanitize_key((string) ($args['status'] ?? ''));
|
||||
|
||||
$repo_args = [
|
||||
'limit' => $per_page,
|
||||
'offset' => ($page - 1) * $per_page,
|
||||
];
|
||||
|
||||
if ($status !== '') {
|
||||
$repo_args['status'] = $status;
|
||||
}
|
||||
|
||||
$repo = \AKSRT\Payment\OrderRepository::get_instance();
|
||||
$orders = $repo->get_user_orders($user_id, $repo_args);
|
||||
$total = $repo->count_user_orders($user_id, $repo_args);
|
||||
|
||||
$items = [];
|
||||
foreach ($orders as $order) {
|
||||
$items[] = $this->decorate_order_view($order);
|
||||
}
|
||||
|
||||
return [
|
||||
'items' => $items,
|
||||
'total' => $total,
|
||||
'total_pages' => max(1, (int) ceil($total / $per_page)),
|
||||
'page' => $page,
|
||||
];
|
||||
return ['items' => [], 'total' => 0, 'total_pages' => 1, 'page' => 1, 'stats' => [], 'product_types' => []];
|
||||
}
|
||||
|
||||
public function get_order_detail_view(int $user_id, string $order_number): ?array {
|
||||
if ($user_id <= 0 || $order_number === '' || !class_exists('\AKSRT\Payment\OrderRepository')) {
|
||||
return null;
|
||||
if (class_exists('\AKSRT\Payment\UserOrders')) {
|
||||
return \AKSRT\Payment\UserOrders::get_instance()->get_order_detail_view($user_id, $order_number);
|
||||
}
|
||||
|
||||
$order = \AKSRT\Payment\OrderRepository::get_instance()->get_user_order($user_id, $order_number);
|
||||
if (!$order) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->decorate_order_view($order, true);
|
||||
return null;
|
||||
}
|
||||
|
||||
public function preview_checkout(int $user_id, array $payload): array {
|
||||
|
||||
+157
-74
@@ -3,8 +3,7 @@
|
||||
* Template Name: AKSRT - Orders
|
||||
* Template Post Type: page
|
||||
*
|
||||
* Enhanced: 5 tabs, logistics timeline, receipt confirm,
|
||||
* review button, re-purchase, gift/discount details (§6.10).
|
||||
* My Orders (权益与消费): multi product_type list + shop fulfillment actions.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
@@ -24,6 +23,8 @@ $per_page = 20;
|
||||
$paged = max(1, (int) ($_GET['order_page'] ?? 1));
|
||||
$tab = sanitize_key((string) ($_GET['tab'] ?? ''));
|
||||
$status = sanitize_key((string) ($_GET['order_status'] ?? ''));
|
||||
$type_filter = sanitize_key((string) ($_GET['order_type'] ?? ''));
|
||||
$section = sanitize_key((string) ($_GET['section'] ?? ''));
|
||||
|
||||
$tab_status_map = [
|
||||
'pending' => \AKSRT\Payment\Order::STATUS_PENDING,
|
||||
@@ -37,14 +38,18 @@ if ($tab !== '' && isset($tab_status_map[$tab])) {
|
||||
}
|
||||
|
||||
$orders_view = function_exists('aksrt_get_orders_page_view')
|
||||
? aksrt_get_orders_page_view($user_id, $paged, $per_page, $status)
|
||||
: ['items' => [], 'total' => 0, 'total_pages' => 1, 'page' => 1];
|
||||
? aksrt_get_orders_page_view($user_id, $paged, $per_page, $status, $type_filter)
|
||||
: ['items' => [], 'total' => 0, 'total_pages' => 1, 'page' => 1, 'stats' => [], 'product_types' => []];
|
||||
|
||||
$orders = (array) ($orders_view['items'] ?? []);
|
||||
$total_pages = max(1, (int) ($orders_view['total_pages'] ?? 1));
|
||||
$orders = (array) ($orders_view['items'] ?? []);
|
||||
$total_pages = max(1, (int) ($orders_view['total_pages'] ?? 1));
|
||||
$stats = is_array($orders_view['stats'] ?? null) ? $orders_view['stats'] : [];
|
||||
$type_options = is_array($orders_view['product_types'] ?? null) ? $orders_view['product_types'] : [];
|
||||
if ($type_options === [] && class_exists('\AKSRT\Core\DomainModel')) {
|
||||
$type_options = array_merge(['' => __('全部类型', 'aksrt')], \AKSRT\Core\DomainModel::order_product_type_labels());
|
||||
}
|
||||
|
||||
$shop = class_exists('\AKSRT\Shop\Shop') ? \AKSRT\Shop\Shop::get_instance() : null;
|
||||
$shipping = class_exists('\AKSRT\Payment\Shipping') ? \AKSRT\Payment\Shipping::get_instance() : null;
|
||||
|
||||
$tabs = [
|
||||
'' => __('全部', 'aksrt'),
|
||||
@@ -53,6 +58,23 @@ $tabs = [
|
||||
'review' => __('待评价', 'aksrt'),
|
||||
'after' => __('售后', 'aksrt'),
|
||||
];
|
||||
|
||||
$base_query = [];
|
||||
if ($type_filter !== '') {
|
||||
$base_query['order_type'] = $type_filter;
|
||||
}
|
||||
if ($section !== '') {
|
||||
$base_query['section'] = $section;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, scalar> $args
|
||||
*/
|
||||
$orders_url = static function (array $args = []) use ($base_query): string {
|
||||
$q = array_merge($base_query, $args);
|
||||
$url = remove_query_arg(['tab', 'order_status', 'order_page', 'order_type', 'section']);
|
||||
return add_query_arg($q, $url);
|
||||
};
|
||||
?>
|
||||
|
||||
<?php if (is_active_sidebar('shop_order_top_fluid')) : ?>
|
||||
@@ -65,7 +87,7 @@ $tabs = [
|
||||
if (function_exists('aksrt_render_commerce_page_header')) {
|
||||
aksrt_render_commerce_page_header([
|
||||
'title' => __('我的订单', 'aksrt'),
|
||||
'subtitle' => __('查看订单状态、物流进度与售后处理。', 'aksrt'),
|
||||
'subtitle' => __('查看商城、付费内容、会员与充值等全部订单。', 'aksrt'),
|
||||
'active' => 'orders',
|
||||
]);
|
||||
} else {
|
||||
@@ -73,77 +95,140 @@ $tabs = [
|
||||
}
|
||||
?>
|
||||
|
||||
<!-- 5-tab navigation -->
|
||||
<?php if (!empty($stats)) : ?>
|
||||
<div class="aksrt-orders-page__stats" data-aksrt-order-stats>
|
||||
<div class="aksrt-orders-page__stat">
|
||||
<span class="aksrt-orders-page__stat-label"><?php esc_html_e('全部订单', 'aksrt'); ?></span>
|
||||
<strong class="aksrt-orders-page__stat-value"><?php echo esc_html((string) (int) ($stats['total'] ?? 0)); ?></strong>
|
||||
</div>
|
||||
<div class="aksrt-orders-page__stat">
|
||||
<span class="aksrt-orders-page__stat-label"><?php esc_html_e('待支付', 'aksrt'); ?></span>
|
||||
<strong class="aksrt-orders-page__stat-value c-red"><?php echo esc_html((string) (int) ($stats['pending'] ?? 0)); ?></strong>
|
||||
</div>
|
||||
<div class="aksrt-orders-page__stat">
|
||||
<span class="aksrt-orders-page__stat-label"><?php esc_html_e('已支付', 'aksrt'); ?></span>
|
||||
<strong class="aksrt-orders-page__stat-value c-green"><?php echo esc_html((string) (int) ($stats['paid'] ?? 0)); ?></strong>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="aksrt-orders-page__extra-nav" data-aksrt-orders-extra-nav>
|
||||
<a href="<?php echo esc_url(add_query_arg(['section' => 'after-sale'])); ?>" class="aksrt-orders-page__extra-link<?php echo (sanitize_key((string) ($_GET['section'] ?? '')) === 'after-sale') ? ' is-active' : ''; ?>"><?php esc_html_e('售后工单', 'aksrt'); ?></a>
|
||||
<a href="<?php echo esc_url(add_query_arg(['section' => 'coupons'])); ?>" class="aksrt-orders-page__extra-link<?php echo (sanitize_key((string) ($_GET['section'] ?? '')) === 'coupons') ? ' is-active' : ''; ?>"><?php esc_html_e('优惠券', 'aksrt'); ?></a>
|
||||
<a href="<?php echo esc_url(add_query_arg(['section' => 'favorites'])); ?>" class="aksrt-orders-page__extra-link<?php echo (sanitize_key((string) ($_GET['section'] ?? '')) === 'favorites') ? ' is-active' : ''; ?>"><?php esc_html_e('收藏', 'aksrt'); ?></a>
|
||||
<a href="<?php echo esc_url($orders_url(['section' => 'after-sale'])); ?>" class="aksrt-orders-page__extra-link<?php echo $section === 'after-sale' ? ' is-active' : ''; ?>"><?php esc_html_e('售后工单', 'aksrt'); ?></a>
|
||||
<a href="<?php echo esc_url($orders_url(['section' => 'coupons'])); ?>" class="aksrt-orders-page__extra-link<?php echo $section === 'coupons' ? ' is-active' : ''; ?>"><?php esc_html_e('优惠券', 'aksrt'); ?></a>
|
||||
<a href="<?php echo esc_url($orders_url(['section' => 'favorites'])); ?>" class="aksrt-orders-page__extra-link<?php echo $section === 'favorites' ? ' is-active' : ''; ?>"><?php esc_html_e('收藏', 'aksrt'); ?></a>
|
||||
</div>
|
||||
|
||||
<?php if ($type_options) : ?>
|
||||
<div class="aksrt-orders-page__type-filters" data-aksrt-order-type-filters>
|
||||
<?php foreach ($type_options as $type_key => $type_label) :
|
||||
$is_type_active = (string) $type_key === (string) $type_filter;
|
||||
$type_url = $orders_url([
|
||||
'order_type' => $type_key !== '' ? $type_key : null,
|
||||
'tab' => $tab !== '' ? $tab : null,
|
||||
'order_page' => 1,
|
||||
]);
|
||||
if ($type_key === '') {
|
||||
$type_url = remove_query_arg('order_type', $type_url);
|
||||
}
|
||||
?>
|
||||
<a href="<?php echo esc_url($type_url); ?>" class="aksrt-orders-page__filter-chip<?php echo $is_type_active ? ' is-active' : ''; ?>">
|
||||
<?php echo esc_html((string) $type_label); ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="aksrt-orders-page__tabs">
|
||||
<?php foreach ($tabs as $tab_key => $tab_label) :
|
||||
$is_active = $tab === $tab_key;
|
||||
$tab_url = $tab_key !== '' ? add_query_arg(['tab' => $tab_key, 'order_page' => 1]) : remove_query_arg(['tab', 'order_status', 'order_page']);
|
||||
?>
|
||||
$tab_url = $tab_key !== ''
|
||||
? $orders_url(['tab' => $tab_key, 'order_page' => 1])
|
||||
: remove_query_arg(['tab', 'order_status', 'order_page'], $orders_url());
|
||||
?>
|
||||
<a href="<?php echo esc_url($tab_url); ?>" class="aksrt-orders-page__tab<?php echo $is_active ? ' is-active' : ''; ?>">
|
||||
<?php echo esc_html($tab_label); ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($orders)) : ?>
|
||||
<?php if ($section === '' && !empty($orders)) : ?>
|
||||
<div class="aksrt-orders-page__list">
|
||||
<?php foreach ($orders as $order) :
|
||||
$on = (string) ($order['order_number'] ?? '');
|
||||
$os = (string) ($order['status'] ?? '');
|
||||
$items = $order['items'] ?? [];
|
||||
?>
|
||||
<article class="aksrt-orders-page__card" data-aksrt-order-card="<?php echo esc_attr($on); ?>">
|
||||
<!-- Card header -->
|
||||
$ptype = (string) ($order['product_type'] ?? '');
|
||||
$is_points = !empty($order['is_points']);
|
||||
$symbol = (string) ($order['currency_symbol'] ?? '¥');
|
||||
$actions = is_array($order['actions'] ?? null) ? $order['actions'] : [];
|
||||
$amount_display = $is_points
|
||||
? esc_html($symbol) . ' ' . esc_html((string) (int) round((float) ($order['amount'] ?? 0)))
|
||||
: '¥' . esc_html(number_format((float) ($order['amount'] ?? 0), 2));
|
||||
?>
|
||||
<article class="aksrt-orders-page__card" data-aksrt-order-card="<?php echo esc_attr($on); ?>" data-product-type="<?php echo esc_attr($ptype); ?>">
|
||||
<div class="aksrt-orders-page__card-head">
|
||||
<span class="aksrt-orders-page__card-type"><?php echo esc_html((string) ($order['product_type_label'] ?? '')); ?></span>
|
||||
<span class="aksrt-orders-page__card-number"><?php echo esc_html($on); ?></span>
|
||||
<time class="aksrt-orders-page__card-time"><?php echo esc_html((string) ($order['created_at'] ?? '')); ?></time>
|
||||
<span class="aksrt-orders-page__card-status"><?php echo esc_html((string) ($order['status_label'] ?? '')); ?></span>
|
||||
</div>
|
||||
|
||||
<!-- Order items -->
|
||||
<div class="aksrt-orders-page__card-items">
|
||||
<?php if (!empty($items)) : ?>
|
||||
<?php if (!empty($items) && is_array($items)) : ?>
|
||||
<?php foreach ($items as $item) : ?>
|
||||
<div class="aksrt-orders-page__card-item" data-product-id="<?php echo esc_attr((string) ($item['product_id'] ?? 0)); ?>">
|
||||
<?php if (!empty($item['image'])) : ?>
|
||||
<img src="<?php echo esc_url((string) $item['image']); ?>" alt="" class="aksrt-orders-page__card-img">
|
||||
<?php elseif (!empty($order['image'])) : ?>
|
||||
<img src="<?php echo esc_url((string) $order['image']); ?>" alt="" class="aksrt-orders-page__card-img">
|
||||
<?php endif; ?>
|
||||
<div class="aksrt-orders-page__card-item-info">
|
||||
<span class="aksrt-orders-page__card-item-title"><?php echo esc_html((string) ($item['product_title'] ?? '')); ?></span>
|
||||
<span class="aksrt-orders-page__card-item-title"><?php echo esc_html((string) ($item['product_title'] ?? $order['product_name'] ?? '')); ?></span>
|
||||
<?php if (!empty($item['options_label'])) : ?>
|
||||
<small class="aksrt-orders-page__card-item-opts"><?php echo esc_html((string) $item['options_label']); ?></small>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="aksrt-orders-page__card-item-price">
|
||||
<strong>¥<?php echo esc_html(number_format((float) ($item['pay_price'] ?? $item['unit_price'] ?? 0), 2)); ?></strong>
|
||||
<strong><?php
|
||||
if ($is_points) {
|
||||
echo esc_html($symbol . ' ' . (string) (int) round((float) ($item['pay_price'] ?? $item['unit_price'] ?? 0)));
|
||||
} else {
|
||||
echo '¥' . esc_html(number_format((float) ($item['pay_price'] ?? $item['unit_price'] ?? 0), 2));
|
||||
}
|
||||
?></strong>
|
||||
<span>×<?php echo esc_html((string) ($item['quantity'] ?? 1)); ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php else : ?>
|
||||
<div class="aksrt-orders-page__card-item" data-product-id="<?php echo esc_attr((string) ($item['product_id'] ?? 0)); ?>">
|
||||
<span class="aksrt-orders-page__card-item-title"><?php echo esc_html((string) ($order['product_name'] ?? __('商品订单', 'aksrt'))); ?></span>
|
||||
<strong>¥<?php echo esc_html(number_format((float) ($order['amount'] ?? 0), 2)); ?></strong>
|
||||
<div class="aksrt-orders-page__card-item" data-product-id="<?php echo esc_attr((string) ($order['product_id'] ?? 0)); ?>">
|
||||
<?php if (!empty($order['image'])) : ?>
|
||||
<img src="<?php echo esc_url((string) $order['image']); ?>" alt="" class="aksrt-orders-page__card-img">
|
||||
<?php endif; ?>
|
||||
<div class="aksrt-orders-page__card-item-info">
|
||||
<span class="aksrt-orders-page__card-item-title"><?php echo esc_html((string) ($order['product_name'] ?? __('订单', 'aksrt'))); ?></span>
|
||||
</div>
|
||||
<div class="aksrt-orders-page__card-item-price">
|
||||
<strong><?php echo $amount_display; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- built with esc_html above ?></strong>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Price summary -->
|
||||
<div class="aksrt-orders-page__card-summary">
|
||||
<span><?php esc_html_e('合计', 'aksrt'); ?></span>
|
||||
<strong>¥<?php echo esc_html(number_format((float) ($order['amount'] ?? 0), 2)); ?></strong>
|
||||
<?php if ((float) ($order['discount'] ?? 0) > 0) : ?>
|
||||
<small>(<?php printf(esc_html__('已优惠 ¥%s', 'aksrt'), number_format((float) $order['discount'], 2)); ?>)</small>
|
||||
<strong><?php echo $amount_display; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></strong>
|
||||
<?php if ((float) ($order['discount'] ?? 0) > 0 && !$is_points) : ?>
|
||||
<small>(<?php
|
||||
printf(
|
||||
/* translators: %s: discount amount */
|
||||
esc_html__('已优惠 ¥%s', 'aksrt'),
|
||||
number_format((float) $order['discount'], 2)
|
||||
);
|
||||
?>)</small>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Logistics timeline (collapsed) -->
|
||||
<?php if (!empty($order['shipping_records'])) : ?>
|
||||
<div class="aksrt-orders-page__timeline" data-aksrt-timeline>
|
||||
<h4 class="aksrt-orders-page__timeline-toggle" data-aksrt-toggle-timeline>
|
||||
@@ -160,29 +245,44 @@ $tabs = [
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="aksrt-orders-page__card-actions">
|
||||
<?php if (!empty($order['actions']['can_pay'])) : ?>
|
||||
<button type="button" class="btn-primary btn-sm" data-aksrt-order-pay="<?php echo esc_attr($on); ?>"><?php esc_html_e('去支付', 'aksrt'); ?></button>
|
||||
<?php if (!empty($actions['can_pay'])) : ?>
|
||||
<button type="button" class="btn-primary btn-sm" data-aksrt-order-pay="<?php echo esc_attr($on); ?>" data-aksrt-order-method="<?php echo esc_attr((string) ($order['payment_method']['value'] ?? '')); ?>"><?php esc_html_e('去支付', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($order['actions']['can_receipt'])) : ?>
|
||||
<?php if (!empty($actions['can_receipt'])) : ?>
|
||||
<button type="button" class="btn-primary btn-sm" data-aksrt-order-receipt="<?php echo esc_attr($on); ?>"><?php esc_html_e('确认收货', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if ($os === \AKSRT\Payment\Order::STATUS_PENDING_REVIEW) : ?>
|
||||
<?php if (!empty($actions['can_review']) || $os === \AKSRT\Payment\Order::STATUS_PENDING_REVIEW) : ?>
|
||||
<button type="button" class="btn-primary btn-sm c-green" data-aksrt-order-review="<?php echo esc_attr($on); ?>"><?php esc_html_e('评价', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($order['actions']['can_cancel'])) : ?>
|
||||
<?php if (!empty($actions['can_cancel'])) : ?>
|
||||
<button type="button" class="btn-ghost btn-sm" data-aksrt-order-cancel="<?php echo esc_attr($on); ?>"><?php esc_html_e('取消', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (in_array($os, [\AKSRT\Payment\Order::STATUS_COMPLETED, \AKSRT\Payment\Order::STATUS_PENDING_REVIEW], true)) : ?>
|
||||
<?php if (!empty($actions['can_rebuy'])) : ?>
|
||||
<button type="button" class="btn-outline btn-sm" data-aksrt-order-rebuy="<?php echo esc_attr($on); ?>"><?php esc_html_e('再次购买', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($order['actions']['can_after_sale'])) : ?>
|
||||
<?php if (!empty($actions['can_after_sale'])) : ?>
|
||||
<button type="button" class="btn-outline btn-sm" data-aksrt-order-after-sale="<?php echo esc_attr($on); ?>"><?php esc_html_e('售后', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($actions['can_view']) && !empty($order['permalink'])) : ?>
|
||||
<a class="btn-outline btn-sm" href="<?php echo esc_url((string) $order['permalink']); ?>"><?php
|
||||
echo $ptype === \AKSRT\Core\DomainModel::ORDER_PRODUCT_RESOURCE
|
||||
? esc_html__('去查看', 'aksrt')
|
||||
: esc_html__('查看', 'aksrt');
|
||||
?></a>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($order['secondary_url']) && !empty($order['secondary_label'])) : ?>
|
||||
<a class="btn-ghost btn-sm" href="<?php echo esc_url((string) $order['secondary_url']); ?>"><?php echo esc_html((string) $order['secondary_label']); ?></a>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($order['time_left'])) : ?>
|
||||
<span class="aksrt-orders-page__countdown" data-aksrt-order-countdown="<?php echo esc_attr((string) $order['time_left']); ?>">
|
||||
<?php printf(esc_html__('%d秒后关闭', 'aksrt'), (int) $order['time_left']); ?>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: %d: seconds */
|
||||
esc_html__('%d秒后关闭', 'aksrt'),
|
||||
(int) $order['time_left']
|
||||
);
|
||||
?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -193,8 +293,11 @@ $tabs = [
|
||||
<?php if ($total_pages > 1) : ?>
|
||||
<div class="aksrt-orders-page__pagination">
|
||||
<?php for ($i = 1; $i <= $total_pages; $i++) :
|
||||
$page_url = add_query_arg(['order_page' => $i]);
|
||||
?>
|
||||
$page_url = $orders_url(array_filter([
|
||||
'tab' => $tab !== '' ? $tab : null,
|
||||
'order_page' => $i,
|
||||
]));
|
||||
?>
|
||||
<?php if ($i === $paged) : ?>
|
||||
<span class="aksrt-orders-page__page-chip is-active"><?php echo esc_html((string) $i); ?></span>
|
||||
<?php else : ?>
|
||||
@@ -203,7 +306,7 @@ $tabs = [
|
||||
<?php endfor; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php else : ?>
|
||||
<?php elseif ($section === '') : ?>
|
||||
<div class="aksrt-orders-page__empty">
|
||||
<i class="fas fa-receipt" aria-hidden="true"></i>
|
||||
<p><?php esc_html_e('暂无订单记录。', 'aksrt'); ?></p>
|
||||
@@ -223,9 +326,11 @@ $tabs = [
|
||||
<div class="aksrt-orders-page__field">
|
||||
<label><?php esc_html_e('售后类型', 'aksrt'); ?></label>
|
||||
<select name="type" class="aksrt-orders-page__select">
|
||||
<?php foreach (\AKSRT\Payment\AfterSale::get_valid_types() as $type_key => $type_label) : ?>
|
||||
<option value="<?php echo esc_attr($type_key); ?>"><?php echo esc_html($type_label); ?></option>
|
||||
<?php endforeach; ?>
|
||||
<?php if (class_exists('\AKSRT\Payment\AfterSale')) : ?>
|
||||
<?php foreach (\AKSRT\Payment\AfterSale::get_valid_types() as $type_key => $type_label) : ?>
|
||||
<option value="<?php echo esc_attr($type_key); ?>"><?php echo esc_html($type_label); ?></option>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="aksrt-orders-page__field">
|
||||
@@ -266,10 +371,7 @@ $tabs = [
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- After-sale tickets panel -->
|
||||
<?php
|
||||
$as_section = sanitize_key((string) ($_GET['section'] ?? ''));
|
||||
if ($as_section === 'after-sale' && class_exists('\AKSRT\Payment\AfterSale')) :
|
||||
<?php if ($section === 'after-sale' && class_exists('\AKSRT\Payment\AfterSale')) :
|
||||
$tickets = \AKSRT\Payment\AfterSale::get_instance()->get_user_tickets($user_id, 50);
|
||||
?>
|
||||
<section class="aksrt-orders-page__aftersale" data-aksrt-aftersale-list>
|
||||
@@ -279,7 +381,7 @@ $tabs = [
|
||||
<?php foreach ($tickets as $t) :
|
||||
$tid = (int) ($t['id'] ?? 0);
|
||||
$events = is_array($t['events'] ?? null) ? $t['events'] : [];
|
||||
$actions = is_array($t['actions'] ?? null) ? $t['actions'] : [];
|
||||
$actions_t = is_array($t['actions'] ?? null) ? $t['actions'] : [];
|
||||
?>
|
||||
<article class="aksrt-as-card" data-aksrt-as-ticket="<?php echo esc_attr((string) $tid); ?>">
|
||||
<div class="aksrt-as-card__head">
|
||||
@@ -290,23 +392,8 @@ $tabs = [
|
||||
<div class="aksrt-as-card__meta muted-2-color">
|
||||
<?php echo esc_html((string) ($t['type_label'] ?? $t['type'] ?? '')); ?>
|
||||
· <?php echo esc_html((string) ($t['created_at'] ?? '')); ?>
|
||||
<?php if (!empty($t['progress_label'])) : ?>
|
||||
· <?php echo esc_html((string) $t['progress_label']); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="aksrt-as-card__reason"><?php echo esc_html((string) ($t['reason'] ?? '')); ?></div>
|
||||
<?php if (!empty($t['return_shipping']['tracking_no'])) : ?>
|
||||
<div class="aksrt-as-card__ship muted-2-color">
|
||||
<?php
|
||||
printf(
|
||||
/* translators: 1 carrier 2 tracking */
|
||||
esc_html__('退货物流:%1$s %2$s', 'aksrt'),
|
||||
esc_html((string) ($t['return_shipping']['carrier'] ?? '')),
|
||||
esc_html((string) $t['return_shipping']['tracking_no'])
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($events) : ?>
|
||||
<ol class="aksrt-as-timeline">
|
||||
<?php foreach (array_reverse($events) as $ev) : ?>
|
||||
@@ -318,10 +405,10 @@ $tabs = [
|
||||
</ol>
|
||||
<?php endif; ?>
|
||||
<div class="aksrt-as-card__actions">
|
||||
<?php if (!empty($actions['can_return_ship'])) : ?>
|
||||
<?php if (!empty($actions_t['can_return_ship'])) : ?>
|
||||
<button type="button" class="btn-primary btn-sm" data-aksrt-return-ship="<?php echo esc_attr((string) $tid); ?>"><?php esc_html_e('填写退货物流', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($actions['can_cancel'])) : ?>
|
||||
<?php if (!empty($actions_t['can_cancel'])) : ?>
|
||||
<button type="button" class="btn-ghost btn-sm" data-aksrt-as-cancel="<?php echo esc_attr((string) $tid); ?>"><?php esc_html_e('取消售后', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -334,8 +421,7 @@ $tabs = [
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Coupon wallet -->
|
||||
<?php if ($as_section === 'coupons') : ?>
|
||||
<?php if ($section === 'coupons') : ?>
|
||||
<section class="aksrt-orders-page__coupons" data-aksrt-coupon-wallet>
|
||||
<h2><?php esc_html_e('优惠券', 'aksrt'); ?></h2>
|
||||
<div class="aksrt-coupon-wallet" data-aksrt-coupon-wallet-list>
|
||||
@@ -344,12 +430,9 @@ $tabs = [
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Favorites section -->
|
||||
<?php
|
||||
$fav_tab = sanitize_key((string) ($_GET['section'] ?? ''));
|
||||
if ($fav_tab === 'favorites' && $shop) :
|
||||
<?php if ($section === 'favorites' && $shop) :
|
||||
$fav_data = $shop->get_user_favorites($user_id);
|
||||
?>
|
||||
?>
|
||||
<section class="aksrt-orders-page__favorites">
|
||||
<h2><?php esc_html_e('收藏商品', 'aksrt'); ?></h2>
|
||||
<?php if (!empty($fav_data['items'])) : ?>
|
||||
|
||||
Reference in New Issue
Block a user