Browse Source

feat: 订单管理页增加手动取消支付分订单功能

- 后端: OrderController 新增 POST /order/payscore/cancel 接口
  接收 outOrderNo 和 reason,直接调用微信支付分取消API
- 前端: 订单管理页搜索栏增加"支付分取消"按钮
  弹出对话框输入 outOrderNo,确认后调用后端取消

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline 2 tuần trước cách đây
mục cha
commit
126e4fac3e

+ 7 - 0
haha-admin-web/src/api/order.ts

@@ -69,4 +69,11 @@ export const reviewRefundApplication = (
   data: { approved: boolean; remark?: string }
 ) => {
   return http.request<Result>("post", `/refund/${id}/review`, { data });
+};
+
+export const cancelPayScoreOrder = (data: {
+  outOrderNo: string;
+  reason?: string;
+}) => {
+  return http.request<Result>("post", "/order/payscore/cancel", { data });
 };

+ 66 - 0
haha-admin-web/src/views/order/index.vue

@@ -6,6 +6,7 @@ import { useRenderIcon } from "@/components/ReIcon/src/hooks";
 import Refresh from "~icons/ep/refresh";
 import View from "~icons/ep/view";
 import Refund from "~icons/ep/money";
+import Cancel from "~icons/ep/delete";
 
 defineOptions({
   name: "OrderManage"
@@ -24,9 +25,33 @@ const {
   resetForm,
   handleDetail,
   handleRefund,
+  handlePayScoreCancel,
   handleSizeChange,
   handleCurrentChange
 } = useOrder(tableRef);
+
+const payscoreCancelVisible = ref(false);
+const payscoreCancelForm = ref({ outOrderNo: "", reason: "" });
+const payscoreCancelLoading = ref(false);
+
+function openPayScoreCancel() {
+  payscoreCancelForm.value = { outOrderNo: "", reason: "" };
+  payscoreCancelVisible.value = true;
+}
+
+async function doPayScoreCancel() {
+  if (!payscoreCancelForm.value.outOrderNo.trim()) {
+    return;
+  }
+  payscoreCancelLoading.value = true;
+  try {
+    await handlePayScoreCancel(payscoreCancelForm.value);
+    payscoreCancelVisible.value = false;
+    payscoreCancelForm.value = { outOrderNo: "", reason: "" };
+  } finally {
+    payscoreCancelLoading.value = false;
+  }
+}
 </script>
 
 <template>
@@ -113,8 +138,49 @@ const {
           重置
         </el-button>
       </el-form-item>
+      <el-form-item>
+        <el-button
+          type="warning"
+          :icon="useRenderIcon(Cancel)"
+          @click="openPayScoreCancel"
+        >
+          支付分取消
+        </el-button>
+      </el-form-item>
     </el-form>
 
+    <el-dialog
+      v-model="payscoreCancelVisible"
+      title="手动取消支付分订单"
+      width="480px"
+      :close-on-click-modal="false"
+    >
+      <el-form :model="payscoreCancelForm" label-width="100px">
+        <el-form-item label="订单号" required>
+          <el-input
+            v-model="payscoreCancelForm.outOrderNo"
+            placeholder="输入 outOrderNo,如 PS1782351340B150534"
+          />
+        </el-form-item>
+        <el-form-item label="取消原因">
+          <el-input
+            v-model="payscoreCancelForm.reason"
+            placeholder="管理员手动取消"
+          />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="payscoreCancelVisible = false">取消</el-button>
+        <el-button
+          type="primary"
+          :loading="payscoreCancelLoading"
+          @click="doPayScoreCancel"
+        >
+          确认取消
+        </el-button>
+      </template>
+    </el-dialog>
+
     <PureTableBar
       title="订单管理"
       :columns="columns"

+ 25 - 1
haha-admin-web/src/views/order/utils/hook.tsx

@@ -6,7 +6,8 @@ import { deviceDetection } from "@pureadmin/utils";
 import {
   getOrderList,
   getOrderById,
-  refundOrder
+  refundOrder,
+  cancelPayScoreOrder
 } from "@/api/order";
 import { type Ref, ref, computed, reactive, onMounted } from "vue";
 import {
@@ -586,6 +587,28 @@ export function useOrder(tableRef: Ref) {
     });
   }
 
+  async function handlePayScoreCancel(cancelForm: { outOrderNo: string; reason?: string }) {
+    const outOrderNo = cancelForm.outOrderNo?.trim();
+    if (!outOrderNo) {
+      message("请输入 outOrderNo", { type: "warning" });
+      return;
+    }
+
+    try {
+      const res = await cancelPayScoreOrder({
+        outOrderNo,
+        reason: cancelForm.reason || undefined
+      });
+      if (res.code === 200) {
+        message(`支付分订单 ${outOrderNo} 取消成功`, { type: "success" });
+      } else {
+        message(res.message || "取消失败", { type: "error" });
+      }
+    } catch (error: any) {
+      message(error?.message || "取消失败", { type: "error" });
+    }
+  }
+
   onMounted(() => {
     onSearch();
   });
@@ -600,6 +623,7 @@ export function useOrder(tableRef: Ref) {
     resetForm,
     handleDetail,
     handleRefund,
+    handlePayScoreCancel,
     handleSizeChange,
     handleCurrentChange
   };

+ 55 - 15
haha-admin/src/main/java/com/haha/admin/controller/OrderController.java

@@ -12,10 +12,15 @@ import com.haha.common.vo.PageResult;
 import com.haha.common.vo.Result;
 import com.haha.entity.Order;
 import com.haha.service.OrderService;
+import com.haha.service.payment.payscore.PayScoreCancelRequest;
+import com.haha.service.payment.payscore.PayScoreResult;
+import com.haha.service.payment.payscore.PayScoreStrategy;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -29,23 +34,24 @@ public class OrderController {
 
     private final OrderService orderService;
 
+    @Autowired(required = false)
+    private PayScoreStrategy payScoreStrategy;
+
     /**
      * 分页查询订单列表
-     * @param queryDTO 查询参数
-     * @return 订单列表
      */
     @RequirePermission("order:read")
     @GetMapping("/list")
     public Result<PageResult<Order>> list(OrderQueryDTO queryDTO) {
-        log.info("订单列表查询参数:page={}, pageSize={}, orderNo={}, deviceId={}, phone={}, status={}, statusList={}", 
-                queryDTO.getPage(), queryDTO.getPageSize(), 
+        log.info("订单列表查询参数:page={}, pageSize={}, orderNo={}, deviceId={}, phone={}, status={}, statusList={}",
+                queryDTO.getPage(), queryDTO.getPageSize(),
                 queryDTO.getOrderNo(), queryDTO.getDeviceId(),
                 queryDTO.getPhone(), queryDTO.getStatus(), queryDTO.getStatusList());
-        
+
         queryDTO.validate();
-        
+
         IPage<Order> orderPage = orderService.getPage(
-                queryDTO.getPage(), 
+                queryDTO.getPage(),
                 queryDTO.getPageSize(),
                 queryDTO.getOrderNo(),
                 queryDTO.getDeviceId(),
@@ -62,8 +68,6 @@ public class OrderController {
 
     /**
      * 获取订单详情(包含商品列表)
-     * @param id 订单ID
-     * @return 订单详情
      */
     @RequirePermission("order:read")
     @GetMapping("/{id}")
@@ -77,9 +81,6 @@ public class OrderController {
 
     /**
      * 获取订单统计数据
-     * @param startDate 开始日期
-     * @param endDate 结束日期
-     * @return 统计数据
      */
     @RequirePermission("order:read")
     @GetMapping("/statistics")
@@ -92,9 +93,6 @@ public class OrderController {
 
     /**
      * 订单退款
-     * @param id 订单ID
-     * @param params 退款参数
-     * @return 退款结果
      */
     @RequirePermission("order:update")
     @Log(module = "订单管理", operation = OperationType.UPDATE, summary = "订单退款")
@@ -103,4 +101,46 @@ public class OrderController {
         boolean success = orderService.refund(id, dto.getReason());
         return success ? Result.success("退款成功", null) : Result.error(500, "退款失败");
     }
+
+    /**
+     * 手动取消支付分订单(通过 outOrderNo 直接调用微信支付分取消API)
+     */
+    @RequirePermission("order:update")
+    @Log(module = "订单管理", operation = OperationType.UPDATE, summary = "手动取消支付分订单")
+    @PostMapping("/payscore/cancel")
+    public Result<Map<String, Object>> cancelPayScore(@RequestBody Map<String, String> params) {
+        if (payScoreStrategy == null) {
+            return Result.error(500, "支付分服务未配置,无法取消");
+        }
+
+        String outOrderNo = params.get("outOrderNo");
+        if (outOrderNo == null || outOrderNo.trim().isEmpty()) {
+            return Result.error(400, "outOrderNo 不能为空");
+        }
+
+        String reason = params.getOrDefault("reason", "管理员手动取消");
+
+        try {
+            PayScoreCancelRequest cancelReq = new PayScoreCancelRequest();
+            cancelReq.setOutOrderNo(outOrderNo.trim());
+            cancelReq.setReason(reason);
+
+            PayScoreResult result = payScoreStrategy.cancelServiceOrder(cancelReq);
+
+            Map<String, Object> data = new HashMap<>();
+            data.put("outOrderNo", outOrderNo);
+            data.put("success", result.isSuccess());
+            if (result.isSuccess()) {
+                data.put("state", result.getState());
+                return Result.success("支付分订单取消成功", data);
+            } else {
+                data.put("errorCode", result.getErrorCode());
+                data.put("errorMsg", result.getErrorMsg());
+                return Result.error(500, "取消失败: " + result.getErrorMsg());
+            }
+        } catch (Exception e) {
+            log.error("手动取消支付分订单失败 - outOrderNo: {}", outOrderNo, e);
+            return Result.error(500, "取消异常: " + e.getMessage());
+        }
+    }
 }