Explorar o código

fix: 支持订单多次部分退款,累计不超过实付金额

- updateOrderRefundStatus 改为累计退款金额,仅当累计 >= 实付金额
  时才将 payStatus 设为 REFUND,部分退款后保持 PAID 允许继续退款
- refund 引擎增加累计金额校验,防止超额退款
- 前端管理后台和小程序退款入口改为检查累计退款金额而非
  payStatus,只有全额退款后才关闭退款入口

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline hai 2 semanas
pai
achega
e1fe47c453

+ 5 - 2
haha-admin-web/src/views/order/utils/hook.tsx

@@ -450,8 +450,11 @@ export function useOrder(tableRef: Ref) {
       message("只有已完成的订单才能退款", { type: "warning" });
       return;
     }
-    if ((row.refundStatus as any) === "REFUNDED" || row.payStatus === "REFUND") {
-      message("该订单已退款", { type: "warning" });
+    // 累计退款已达实付金额则不允许继续退款
+    const paidAmount = row.paidAmount || row.totalAmount || 0;
+    const refundedAmount = (row as any).refundAmount || 0;
+    if (refundedAmount >= paidAmount) {
+      message("该订单已全额退款", { type: "warning" });
       return;
     }
 

+ 3 - 0
haha-mp/src/api/order.ts

@@ -41,6 +41,9 @@ export interface OrderInfo {
   payStatus: string;
   payChannel?: string; // 支付渠道:wechat/alipay/balance/wechat_payscore
   payScoreOrderId?: string; // 微信支付分服务订单号
+  refundStatus?: string; // 退款状态
+  refundAmount?: number; // 累计退款金额
+  currentRefundId?: string; // 当前退款记录ID
   status: number;
   statusText?: string;
   createTime: string;

+ 13 - 4
haha-mp/src/utils/order.ts

@@ -19,24 +19,33 @@ export const getStatusText = (status: number): string => {
 
 /**
  * 判断订单是否可以申请退款
- * 只有已完成的订单可以退款,且订单完成时间不能超过7天
+ * - 只有已完成的订单可以退款
+ * - 订单完成时间不能超过7天
+ * - 累计退款金额小于实付金额(未全额退款)
  */
 export const canRefund = (order: OrderInfo): boolean => {
   if (order.status !== 1) {
     return false;
   }
-  
+
+  // 累计退款已达实付金额,不允许继续退款
+  const paidAmount = order.paidAmount || order.totalAmount || 0;
+  const refundedAmount = order.refundAmount || 0;
+  if (refundedAmount >= paidAmount) {
+    return false;
+  }
+
   if (order.payTime) {
     const payTime = new Date(order.payTime);
     const now = new Date();
     const diffTime = now.getTime() - payTime.getTime();
     const diffDays = diffTime / (1000 * 60 * 60 * 24);
-    
+
     if (diffDays > 7) {
       return false;
     }
   }
-  
+
   return true;
 };
 

+ 1 - 2
haha-service/src/main/java/com/haha/service/impl/RefundServiceImpl.java

@@ -120,10 +120,9 @@ public class RefundServiceImpl extends ServiceImpl<RefundMapper, Refund> impleme
             }
         }
 
-        // 更新订单关联
+        // 更新订单关联(不设置 refundAmount,由 updateOrderRefundStatus 计算累计值)
         order.setCurrentRefundId(refund.getId());
         order.setRefundStatus(RefundStatus.PENDING.getCode());
-        order.setRefundAmount(finalRefundAmount);
         order.setRefundReason(reason);
         orderService.updateById(order);
 

+ 32 - 4
haha-service/src/main/java/com/haha/service/payment/impl/PaymentServiceImpl.java

@@ -195,6 +195,21 @@ public class PaymentServiceImpl implements PaymentService {
         BigDecimal actualRefundAmount = refundAmount != null ? refundAmount
                 : (order.getPaidAmount() != null ? order.getPaidAmount() : order.getTotalAmount());
 
+        // 校验累计退款金额不超过实付金额
+        BigDecimal paidAmount = order.getPaidAmount() != null ? order.getPaidAmount() : order.getTotalAmount();
+        BigDecimal existingRefund = order.getRefundAmount() != null ? order.getRefundAmount() : BigDecimal.ZERO;
+        BigDecimal totalAfterRefund = existingRefund.add(actualRefundAmount);
+        if (totalAfterRefund.compareTo(paidAmount) > 0) {
+            BigDecimal remaining = paidAmount.subtract(existingRefund);
+            log.error("退款失败: 累计退款金额超过实付金额, orderId={}, existingRefund={}, requestAmount={}, remaining={}",
+                    orderId, existingRefund, actualRefundAmount, remaining);
+            return RefundResult.builder()
+                    .success(false)
+                    .errorCode("REFUND_AMOUNT_EXCEEDED")
+                    .errorMsg("退款金额超过可退金额,已退" + existingRefund + "元,最多可退" + remaining + "元")
+                    .build();
+        }
+
         // 5. 根据订单的 payChannel 获取支付渠道
         PaymentChannel channel = PaymentChannel.fromCode(order.getPayChannel());
 
@@ -601,14 +616,27 @@ public class PaymentServiceImpl implements PaymentService {
             }
         }
 
+        // 累计退款金额
+        BigDecimal existingRefund = order.getRefundAmount() != null ? order.getRefundAmount() : BigDecimal.ZERO;
+        BigDecimal cumulativeAmount = existingRefund.add(amount);
+
         order.setRefundStatus(RefundStatus.REFUNDED.getCode());
-        order.setRefundAmount(amount);
+        order.setRefundAmount(cumulativeAmount);
         order.setRefundTime(LocalDateTime.now());
         order.setRefundReason(reason);
-        order.setPayStatus(OrderConstants.PAY_STATUS_REFUND);
+
+        // 仅当累计退款 >= 实付金额时,才标记为全部退款
+        BigDecimal paidAmount = order.getPaidAmount() != null ? order.getPaidAmount() : order.getTotalAmount();
+        if (cumulativeAmount.compareTo(paidAmount) >= 0) {
+            order.setPayStatus(OrderConstants.PAY_STATUS_REFUND);
+            log.info("订单全部退款完成 - orderId={}, cumulativeAmount={}, paidAmount={}",
+                    order.getId(), cumulativeAmount, paidAmount);
+        }
+        // 部分退款时保持 payStatus = PAID,允许继续退款
+
         orderService.updateById(order);
-        log.info("订单退款状态已更新 - orderId={}, refundAmount={}, payStatus={}",
-                order.getId(), amount, OrderConstants.PAY_STATUS_REFUND);
+        log.info("订单退款状态已更新 - orderId={}, refundAmount={}(累计), payStatus={}",
+                order.getId(), cumulativeAmount, order.getPayStatus());
     }
     
     /**