feat(admin): 订单明细页已支付订单新增「退款」按钮
操作栏「详情」右侧为已支付订单渲染「退款」按钮:点击经 aksrt_pay_refund_order 把订单推到已退款。 退款沿既有的受闸路径执行:OrderRepository::update_status 要求 allow_refund,且 can_finalize_order_refund 要求该售后工单仍为待审核/处理中。因此工单号由服务端按订单号反查 find_active 得到,客户端只提交订单号;没有进行中的工单时整次操作中止并给出可操作提示,不会出现订单已退款但无工单依据的状态。 按钮仅在已支付订单上渲染——已退款是已支付的唯一后继状态,其他状态点击也只会在服务端被拒。附带清理 AfterSaleService::find_active 上方一处早已错配的 @param 文档注释(描述的是 \,与该函数签名无关)。
This commit is contained in:
@@ -30,6 +30,7 @@ class PayOrders {
|
||||
add_action('admin_post_aksrt_pay_update_order_note', [$this, 'handle_update_order_note']);
|
||||
add_action('admin_post_aksrt_pay_clear_closed_orders', [$this, 'handle_clear_closed_orders']);
|
||||
add_action('admin_post_aksrt_pay_delete_order', [$this, 'handle_delete_order']);
|
||||
add_action('admin_post_aksrt_pay_refund_order', [$this, 'handle_refund_order']);
|
||||
add_action('admin_post_aksrt_after_sale_update', [$this, 'handle_after_sale_status']);
|
||||
}
|
||||
|
||||
@@ -254,6 +255,15 @@ class PayOrders {
|
||||
echo '<td>' . esc_html((string) ($row['created_at'] ?? '')) . '<br><span class="description">' . esc_html((string) ($row['paid_at'] ?? '') ?: '—') . '</span></td>';
|
||||
echo '<td style="display:flex;gap:6px;flex-wrap:wrap;align-items:center;">';
|
||||
echo '<a class="button button-small" href="' . esc_url($this->page_url('aksrt_orders', ['order' => $order_number])) . '">' . esc_html__('详情', 'aksrt') . '</a>';
|
||||
// 已支付是已退款的唯一前置状态;退款仍需落到一条进行中的售后工单上。
|
||||
if ($status_key === \AKSRT\Core\DomainModel::ORDER_STATUS_PAID) {
|
||||
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="display:inline;" onsubmit="return confirm(\'' . esc_js(__('确认将该订单标记为已退款?不可恢复。', 'aksrt')) . '\');">';
|
||||
echo '<input type="hidden" name="action" value="aksrt_pay_refund_order" />';
|
||||
echo '<input type="hidden" name="order_number" value="' . esc_attr($order_number) . '" />';
|
||||
wp_nonce_field('aksrt_pay_refund_order_' . $order_number);
|
||||
submit_button(__('退款', 'aksrt'), 'small', '', false);
|
||||
echo '</form>';
|
||||
}
|
||||
$this->render_order_quick_status_buttons($order_number, $status_key);
|
||||
if ($status_key === 'closed') {
|
||||
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '" style="display:inline;" onsubmit="return confirm(\'' . esc_js(__('确认永久删除此订单?不可恢复。', 'aksrt')) . '\');">';
|
||||
@@ -631,6 +641,47 @@ class PayOrders {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单列表「退款」:把已支付订单推到已退款。退款必须落到一条进行中的售后工单上,
|
||||
* 工单号由服务端按订单号反查,不经客户端传递。
|
||||
*/
|
||||
public function handle_refund_order(): void {
|
||||
if (!current_user_can(self::CAPABILITY)) {
|
||||
wp_die(esc_html__('权限不足', 'aksrt'));
|
||||
}
|
||||
|
||||
$order_number = isset($_POST['order_number']) ? sanitize_text_field((string) wp_unslash($_POST['order_number'])) : '';
|
||||
check_admin_referer('aksrt_pay_refund_order_' . $order_number);
|
||||
|
||||
$code = 'order_refund_failed';
|
||||
if ($order_number !== '' && class_exists('\AKSRT\Payment\OrderRepository')) {
|
||||
$repo = \AKSRT\Payment\OrderRepository::get_instance();
|
||||
$order = $repo->get($order_number);
|
||||
if (!is_array($order)) {
|
||||
$code = 'order_refund_missing';
|
||||
} elseif ((string) ($order['status'] ?? '') !== \AKSRT\Core\DomainModel::ORDER_STATUS_PAID) {
|
||||
$code = 'order_refund_not_paid';
|
||||
} else {
|
||||
$ticket = class_exists('\AKSRT\Payment\AfterSaleService')
|
||||
? \AKSRT\Payment\AfterSaleService::get_instance()->find_active($order_number)
|
||||
: null;
|
||||
if (!is_array($ticket)) {
|
||||
$code = 'order_refund_no_ticket';
|
||||
} else {
|
||||
$ok = $repo->update_status($order_number, \AKSRT\Core\DomainModel::ORDER_STATUS_REFUNDED, [
|
||||
'allow_refund' => true,
|
||||
'after_sale_id' => (int) ($ticket['id'] ?? 0),
|
||||
'note' => __('管理员在订单列表中标记退款', 'aksrt'),
|
||||
]);
|
||||
$code = $ok ? 'order_refunded' : 'order_refund_failed';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wp_safe_redirect($this->page_url('aksrt_orders', ['aksrt_pay_msg' => $code]));
|
||||
exit;
|
||||
}
|
||||
|
||||
public function handle_update_order_note(): void {
|
||||
if (!current_user_can(self::CAPABILITY)) {
|
||||
wp_die(esc_html__('权限不足', 'aksrt'));
|
||||
@@ -769,6 +820,11 @@ class PayOrders {
|
||||
$map = [
|
||||
'order_updated' => __('订单状态已更新。', 'aksrt'),
|
||||
'order_failed' => __('订单状态更新失败。', 'aksrt'),
|
||||
'order_refunded' => __('订单已标记为已退款。', 'aksrt'),
|
||||
'order_refund_failed' => __('订单标记退款失败。', 'aksrt'),
|
||||
'order_refund_missing' => __('订单不存在。', 'aksrt'),
|
||||
'order_refund_not_paid' => __('仅已支付订单可以标记退款。', 'aksrt'),
|
||||
'order_refund_no_ticket' => __('该订单没有进行中的售后工单,无法标记退款;请先在「售后服务」中处理。', 'aksrt'),
|
||||
'order_note_updated' => __('订单备注已保存。', 'aksrt'),
|
||||
'order_note_failed' => __('订单备注保存失败。', 'aksrt'),
|
||||
'orders_cleared' => sprintf(
|
||||
|
||||
@@ -313,8 +313,8 @@ final class AfterSaleService {
|
||||
return $this->get($id);
|
||||
}
|
||||
|
||||
/** @param array<int,array<string,mixed>> $entries */
|
||||
private function find_active(string $number): ?array {
|
||||
/** 该订单当前仍在处理中的售后工单(待审核/处理中),退款闸门只认它。 */
|
||||
public function find_active(string $number): ?array {
|
||||
global $wpdb;
|
||||
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM {$this->table()} WHERE order_number = %s AND status IN ('pending','processing') ORDER BY id DESC LIMIT 1", $number), ARRAY_A);
|
||||
return is_array($row) ? $this->normalize($row) : null;
|
||||
|
||||
Reference in New Issue
Block a user