feat(after-sale): 售后面板新增「标记订单已退款」勾选项

展开的操作面板在状态下拉与备注旁新增「标记订单已退款」勾选框:勾选后点击确认,会先把关联订单推到已退款,再更新售后工单状态。订单本已退款时勾选框呈现为不可改的已选状态,且不提交该标记。

顺序不能反:OrderRepository::update_status 的退款闸门要求售后工单仍为待审核/处理中,一旦 set_status 先执行就不再放行。因此退款失败时整次操作中止,避免出现「工单已关、订单未退」的半完成状态。订单号由服务端按 after_sale_id 反查,不经客户端传递。

AfterSaleService::get() 由 private 改为 public 以支撑该反查。dev harness 扩展为可驱动 handler 的六个场景,覆盖勾选/未勾选、重复退款、非已支付订单、已关闭工单与非法状态。
This commit is contained in:
kate522 committed 2026-10-08 21:03:23 +08:00
1 parent b733e41993
commit 5fd8c6fc31
4 files changed
+187 -15

No files matched your search

+45 -2
View File
@@ -417,6 +417,7 @@ class PayOrders {
. '.aksrt-as-review-main{flex:1 1 460px;min-width:0;display:flex;flex-wrap:wrap;align-items:flex-end;gap:10px 12px;}'
. '.aksrt-as-review-main p{margin:0;}'
. '.aksrt-status-select{min-width:150px;}'
. '.aksrt-as-refund-flag{display:inline-flex;align-items:center;gap:6px;white-space:nowrap;margin:0;}'
. '.aksrt-as-review textarea{flex:1 1 240px;min-width:0;box-sizing:border-box;margin:0;}'
. '.aksrt-as-review-actions{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end;margin-left:auto;}'
. '.aksrt-as-field--wide{grid-column:1/-1;}'
@@ -462,6 +463,7 @@ class PayOrders {
if ($desc_cell === '') $desc_cell = '—';
$status_label = $status_labels[(string) $item['status']] ?? (string) $item['status'];
$admin_note = (string) ($item['admin_note'] ?? '');
$order_refunded = is_array($order) && (string) ($order['status'] ?? '') === \AKSRT\Core\DomainModel::ORDER_STATUS_REFUNDED;
// 摘要行 + 手风琴详情行:点击「操作」展开整条记录的完整信息与状态面板
echo '<tr class="aksrt-as-row">';
echo '<td>' . esc_html((string) $id) . '<br><small>' . esc_html((string) ($item['created_at'] ?? '')) . '</small></td>';
@@ -509,6 +511,10 @@ class PayOrders {
echo '<p><select class="aksrt-status-select">' . $status_options . '</select></p>';
echo '<textarea class="aksrt-review-note" rows="2" placeholder="' . esc_attr__('填写处理备注(可留空)', 'aksrt') . '"></textarea>';
echo '</div>';
// 勾选后在确认时把关联订单推到已退款;订单本已退款时呈现为不可改的已选状态
echo '<label class="aksrt-as-refund-flag"><input type="checkbox" class="aksrt-mark-order-refunded" value="1"'
. ($order_refunded ? ' checked="checked" disabled' : '')
. ' /> ' . esc_html__('标记订单已退款', 'aksrt') . '</label>';
echo '<div class="aksrt-as-review-actions">';
echo '<button type="button" class="button aksrt-after-sale-cancel">' . esc_html__('取消', 'aksrt') . '</button>';
echo '<button type="button" class="button button-primary aksrt-after-sale-confirm"'
@@ -543,11 +549,15 @@ class PayOrders {
. 'var panel=a.closest(".aksrt-as-detail-row");'
. 'var sel=panel?panel.querySelector(".aksrt-status-select"):null;'
. 'var noteEl=panel?panel.querySelector(".aksrt-review-note"):null;'
. 'var refundEl=panel?panel.querySelector(".aksrt-mark-order-refunded"):null;'
. 'var markRefund=!!(refundEl&&refundEl.checked&&!refundEl.disabled);'
. 'var opt=sel?sel.options[sel.selectedIndex]:null;'
. 'if(!sel||!opt||opt.disabled){alert(' . wp_json_encode(__('请选择售后状态。', 'aksrt')) . ');return;}'
. 'if(!confirm(tConfirmStatus+"「"+opt.text+"」?"))return;'
. 'var confirmText=tConfirmStatus+"「"+opt.text+"」?";'
. 'if(markRefund)confirmText+=' . wp_json_encode(__('同时会将关联订单标记为已退款。', 'aksrt')) . ';'
. 'if(!confirm(confirmText))return;'
. 'var f=document.createElement("form");f.method="post";f.action=postUrl;'
. 'var fields={action:"aksrt_after_sale_update",after_sale_id:a.getAttribute("data-id"),after_sale_status:sel.value,_wpnonce:a.getAttribute("data-nonce"),admin_note:noteEl?noteEl.value:""};'
. 'var fields={action:"aksrt_after_sale_update",after_sale_id:a.getAttribute("data-id"),after_sale_status:sel.value,mark_order_refunded:markRefund?"1":"",_wpnonce:a.getAttribute("data-nonce"),admin_note:noteEl?noteEl.value:""};'
. 'Object.keys(fields).forEach(function(k){var i=document.createElement("input");i.type="hidden";i.name=k;i.value=fields[k];f.appendChild(i);});'
. 'document.body.appendChild(f);f.submit();'
. '});'
@@ -560,12 +570,43 @@ class PayOrders {
check_admin_referer('aksrt_after_sale_status_' . $id);
$status = isset($_POST['after_sale_status']) ? sanitize_key((string) wp_unslash($_POST['after_sale_status'])) : '';
$note = isset($_POST['admin_note']) ? sanitize_textarea_field((string) wp_unslash($_POST['admin_note'])) : '';
$mark_refund = isset($_POST['mark_order_refunded']) && (string) wp_unslash($_POST['mark_order_refunded']) === '1';
// 勾选「标记订单已退款」时先落订单状态:售后工单一旦离开 pending/processing,
// OrderRepository 的退款闸门就不再放行,所以顺序不能反。
if ($mark_refund && $this->mark_related_order_refunded($id) === 'failed') {
wp_safe_redirect($this->page_url(self::PAGE_AFTER_SALES_SLUG, ['aksrt_pay_msg' => 'after_sale_order_refund_failed']));
exit;
}
$result = \AKSRT\Payment\AfterSaleService::get_instance()->set_status($id, $status, $note);
$args = ['aksrt_pay_msg' => is_wp_error($result) ? 'after_sale_update_failed' : 'after_sale_updated'];
if (is_wp_error($result)) $args['after_sale_error'] = substr($result->get_error_message(), 0, 240);
elseif ($mark_refund) $args['aksrt_pay_msg'] = 'after_sale_updated_order_refunded';
wp_safe_redirect($this->page_url(self::PAGE_AFTER_SALES_SLUG, $args)); exit;
}
/**
* 把售后申请关联的订单推到已退款。仅在该工单仍为待审核/处理中时放行,
* 避免对已关闭的工单或非已支付订单误标退款。
*
* @return string 'done' | 'failed'
*/
private function mark_related_order_refunded(int $after_sale_id): string {
if (!class_exists('\AKSRT\Payment\AfterSaleService') || !class_exists('\AKSRT\Payment\OrderRepository')) return 'failed';
$service = \AKSRT\Payment\AfterSaleService::get_instance();
$row = $service->get($after_sale_id);
$order_number = is_array($row) ? (string) ($row['order_number'] ?? '') : '';
if ($order_number === '' || !$service->can_finalize_order_refund($after_sale_id, $order_number)) return 'failed';
$repo = \AKSRT\Payment\OrderRepository::get_instance();
$order = $repo->get($order_number);
if (is_array($order) && (string) ($order['status'] ?? '') === \AKSRT\Core\DomainModel::ORDER_STATUS_REFUNDED) return 'done';
return $repo->update_status($order_number, \AKSRT\Core\DomainModel::ORDER_STATUS_REFUNDED, [
'allow_refund' => true,
'after_sale_id' => $after_sale_id,
]) ? 'done' : 'failed';
}
public function handle_update_order_status(): void {
if (!current_user_can(self::CAPABILITY)) {
wp_die(esc_html__('权限不足', 'aksrt'));
@@ -745,6 +786,8 @@ class PayOrders {
'after_sale_created' => __('售后工单已创建。', 'aksrt'),
'after_sale_create_failed' => __('售后工单创建失败。', 'aksrt'),
'after_sale_updated' => __('售后状态已更新。', 'aksrt'),
'after_sale_updated_order_refunded' => __('售后状态已更新,关联订单已标记为已退款。', 'aksrt'),
'after_sale_order_refund_failed' => __('关联订单标记已退款失败:需售后工单仍为待审核/处理中,且订单为已支付。', 'aksrt'),
'after_sale_update_failed' => __('售后状态更新失败,请查看申请状态和错误原因。', 'aksrt'),
];
if (!isset($map[$code])) {