Browse Source

code review 退款逻辑

YunaiV 3 years ago
parent
commit
67aaf28832

+ 1 - 2
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/convert/order/PayRefundCoreConvert.java

@@ -12,8 +12,6 @@ public interface PayRefundCoreConvert {
 
     PayRefundCoreConvert INSTANCE = Mappers.getMapper(PayRefundCoreConvert.class);
 
-
-
     //TODO 太多需要处理了, 暂时不用
     @Mappings(value = {
             @Mapping(source = "amount", target = "payAmount"),
@@ -21,4 +19,5 @@ public interface PayRefundCoreConvert {
             @Mapping(target = "status",ignore = true)
     })
     PayRefundDO convert(PayOrderDO orderDO);
+
 }

+ 7 - 5
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayRefundDO.java

@@ -3,6 +3,8 @@ package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order;
 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum;
 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import com.baomidou.mybatisplus.annotation.TableId;
@@ -79,7 +81,6 @@ public class PayRefundDO extends BaseDO {
      */
     private Long orderId;
 
-
     /**
      * 交易订单号,根据规则生成
      * 调用支付渠道时,使用该字段作为对接的订单号。
@@ -110,7 +111,6 @@ public class PayRefundDO extends BaseDO {
      * 防止该笔交易重复退款。支付宝会保证同样的退款请求号多次请求只会退一次。
      * 退款单请求号,根据规则生成
      * 例如说,R202109181134287570000
-     *
      */
     private String merchantRefundNo;
 
@@ -129,19 +129,22 @@ public class PayRefundDO extends BaseDO {
     /**
      * 退款状态
      *
+     * 枚举 {@link PayRefundStatusEnum}
      */
     private Integer status;
 
     /**
      * 退款类型(部分退款,全部退款)
+     *
+     * 枚举 {@link PayRefundTypeEnum}
      */
     private Integer type;
     /**
-     * 支付金额,单位
+     * 支付金额,单位:
      */
     private Long payAmount;
     /**
-     * 退款金额,单位
+     * 退款金额,单位:
      */
     private Long refundAmount;
 
@@ -150,7 +153,6 @@ public class PayRefundDO extends BaseDO {
      */
     private String reason;
 
-
     /**
      * 用户 IP
      */

+ 2 - 1
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/order/PayRefundCoreMapper.java

@@ -17,6 +17,7 @@ public interface PayRefundCoreMapper extends BaseMapperX<PayRefundDO> {
     }
 
     default  PayRefundDO selectByTradeNoAndMerchantRefundNo(String tradeNo, String merchantRefundNo){
-        return  selectOne("trade_no", tradeNo, "merchant_refund_no", merchantRefundNo);
+        return selectOne("trade_no", tradeNo, "merchant_refund_no", merchantRefundNo);
     }
+
 }

+ 1 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayRefundStatusEnum.java

@@ -6,6 +6,7 @@ import lombok.Getter;
 @Getter
 @AllArgsConstructor
 public enum PayRefundStatusEnum {
+
     CREATE(0, "退款订单生成"),
     SUCCESS(1, "退款成功"),
     FAILURE(2, "退款失败"),

+ 3 - 2
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/dto/PayRefundReqDTO.java

@@ -16,8 +16,9 @@ import lombok.experimental.Accessors;
 @AllArgsConstructor
 public class PayRefundReqDTO {
 
+    // TODO @jason:增加下 validation 注解哈
     /**
-     * 支付订单编号自增
+     * 支付订单编号
      */
     private Long payOrderId;
 
@@ -31,10 +32,10 @@ public class PayRefundReqDTO {
      */
     private String reason;
 
-
     /**
      * 商户退款订单号
      */
+    // TODO @jason:merchantRefundNo=》merchantRefundId,保持和 PayOrder 的 merchantOrderId 一致哈
     private String merchantRefundNo;
 
     /**

+ 4 - 2
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/dto/PayRefundRespDTO.java

@@ -21,10 +21,11 @@ public class PayRefundRespDTO {
      * 退款处理中和退款成功  返回  1
      * 失败和其他情况 返回 2
      */
+    // TODO @jason:这个 result,可以使用 CommonResult 里呢
     private Integer channelReturnResult;
 
     /**
-     * 渠道返回code
+     * 渠道返回 code
      */
     private String channelReturnCode;
 
@@ -34,7 +35,8 @@ public class PayRefundRespDTO {
     private String  channelReturnMsg;
 
     /**
-     * 支付退款单编号, 自增
+     * 支付退款单编号,自增
      */
     private Long refundId;
+
 }

+ 40 - 32
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundCoreServiceImpl.java

@@ -11,20 +11,20 @@ import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderExtensio
 import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundCoreMapper;
 import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum;
 import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum;
-import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService;
-import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
-import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundReqDTO;
-import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundNotifyDTO;
-import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum;
 import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum;
 import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
+import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum;
 import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayAppCoreService;
 import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayChannelCoreService;
+import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService;
+import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
 import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundCoreService;
+import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundReqDTO;
 import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundRespDTO;
 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
 import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundNotifyDTO;
 import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO;
 import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum;
@@ -34,7 +34,8 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
-import java.util.*;
+import java.util.Date;
+import java.util.Objects;
 
 import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.*;
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -90,17 +91,19 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
 
         PayOrderExtensionDO orderExtensionDO = payOrderExtensionCoreMapper.selectById(order.getSuccessExtensionId());
         PayRefundDO payRefundDO = payRefundCoreMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(), req.getMerchantRefundNo());
-        //构造渠道的统一的退款请求参数
+        // 构造渠道的统一的退款请求参数
         PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO();
         if(Objects.nonNull(payRefundDO)){
-            //退款订单已经提交过。
+            // 退款订单已经提交过。
             //TODO 校验相同退款单的金额
-            if(Objects.equals(PayRefundStatusEnum.SUCCESS.getStatus(), payRefundDO.getStatus())
-                || Objects.equals(PayRefundStatusEnum.CLOSE.getStatus(), payRefundDO.getStatus())){
+            // TODO @jason:咱要不封装一个 ObjectUtils.equalsAny
+            if (Objects.equals(PayRefundStatusEnum.SUCCESS.getStatus(), payRefundDO.getStatus())
+                || Objects.equals(PayRefundStatusEnum.CLOSE.getStatus(), payRefundDO.getStatus())) {
                 //已成功退款
                throw exception(PAY_REFUND_SUCCEED);
-            }else{
-                //保证商户退款单不变,重复向渠道发起退款。渠道保持幂等
+            } else{
+                // TODO @jason:这里不用 else,简洁一些
+                // 保证商户退款单不变,重复向渠道发起退款。渠道保持幂等
                 unifiedReqDTO.setUserIp(req.getUserIp())
                              .setAmount(payRefundDO.getRefundAmount())
                              .setChannelOrderNo(payRefundDO.getChannelOrderNo())
@@ -109,7 +112,8 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
                              .setReason(payRefundDO.getReason());
             }
         }else{
-            //新生成退款单。 退款单入库 退款单状态:生成
+            // 新生成退款单。 退款单入库 退款单状态:生成
+            // TODO @jason:封装一个小方法。插入退款单
             payRefundDO = PayRefundDO.builder().channelOrderNo(order.getChannelOrderNo())
                     .appId(order.getAppId())
                     .channelOrderNo(order.getChannelOrderNo())
@@ -130,6 +134,7 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
                     .type(refundType.getStatus())
                     .build();
             payRefundCoreMapper.insert(payRefundDO);
+            // TODO @jason:这块的逻辑,和已存在的这块,貌似是统一的?
             unifiedReqDTO.setUserIp(req.getUserIp())
                     .setAmount(payRefundDO.getRefundAmount())
                     .setChannelOrderNo(payRefundDO.getChannelOrderNo())
@@ -137,19 +142,20 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
                     .setRefundReqNo(payRefundDO.getMerchantRefundNo())
                     .setReason(req.getReason());
         }
-        //向渠道发起退款申请
+        // 向渠道发起退款申请
         PayRefundUnifiedRespDTO refundUnifiedRespDTO = client.unifiedRefund(unifiedReqDTO);
-        //构造退款申请返回对象
+        // 构造退款申请返回对象
         PayRefundRespDTO respDTO = new PayRefundRespDTO();
-        if(refundUnifiedRespDTO.getChannelResp() == PayChannelRefundRespEnum.SUCCESS
-            ||refundUnifiedRespDTO.getChannelResp() == PayChannelRefundRespEnum.PROCESSING ){
-            //成功处理, 在退款通知中处理, 这里不处理
+        if (refundUnifiedRespDTO.getChannelResp() == PayChannelRefundRespEnum.SUCCESS
+            ||refundUnifiedRespDTO.getChannelResp() == PayChannelRefundRespEnum.PROCESSING) {
+            // 成功处理,在退款通知中处理, 这里不处理
             respDTO.setChannelReturnResult(PayChannelRefundRespEnum.SUCCESS.getStatus());
             respDTO.setRefundId(payRefundDO.getId());
         }else {
-            //失败返回错误给前端,可以重新发起退款,保证退款请求号(这里是商户退款单号), 避免重复退款。
+            // 失败返回错误给前端,可以重新发起退款,保证退款请求号(这里是商户退款单号), 避免重复退款。
+            // TODO @jason:失败的话,是不是可以跑出 ServiceException 业务异常。这样就是成功返回 refundId,失败业务异常
             respDTO.setChannelReturnResult(PayChannelRefundRespEnum.FAILURE.getStatus());
-            //更新退款单状态
+            // 更新退款单状态
             PayRefundDO updatePayRefund = new PayRefundDO();
             updatePayRefund.setId(payRefundDO.getId())
                     .setChannelErrorMsg(refundUnifiedRespDTO.getChannelMsg())
@@ -181,35 +187,37 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
             payRefundSuccess(refundNotify);
         } else {
             //TODO 支付异常, 支付宝似乎没有支付异常的通知。
+            // TODO @jason:那这里可以考虑打个 error logger
         }
     }
 
     private void payRefundSuccess(PayRefundNotifyDTO refundNotify) {
+        // 校验退款单存在
         PayRefundDO refundDO = payRefundCoreMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(), refundNotify.getReqNo());
         if (refundDO == null) {
-            log.error("不存在 seqNo 为{} 的支付退款单",refundNotify.getReqNo());
+            log.error("[payRefundSuccess][不存在 seqNo 为{} 的支付退款单]", refundNotify.getReqNo());
             throw exception(PAY_REFUND_NOT_FOUND);
         }
-        Long refundAmount = refundDO.getRefundAmount();
+
+        // 计算订单的状态。如果全部退款,则订单处于关闭。TODO @jason:建议这里按照金额来判断,因为可能退款多次
         Integer type = refundDO.getType();
         PayOrderStatusEnum orderStatus = PayOrderStatusEnum.SUCCESS;
-        if(PayRefundTypeEnum.ALL.getStatus().equals(type)){
+        if (PayRefundTypeEnum.ALL.getStatus().equals(type)){
             orderStatus = PayOrderStatusEnum.CLOSED;
         }
-        // 更新支付订单
-        PayOrderDO payOrderDO = payOrderCoreMapper.selectById(refundDO.getOrderId());
         // 需更新已退金额
+        PayOrderDO payOrderDO = payOrderCoreMapper.selectById(refundDO.getOrderId());
         Long refundedAmount = payOrderDO.getRefundAmount();
+        // 更新支付订单
         PayOrderDO updateOrderDO = new PayOrderDO();
         updateOrderDO.setId(refundDO.getOrderId())
-                .setRefundAmount(refundedAmount + refundAmount)
+                .setRefundAmount(refundedAmount + refundDO.getRefundAmount())
                 .setStatus(orderStatus.getStatus())
-                .setRefundTimes(payOrderDO.getRefundTimes()+1)
+                .setRefundTimes(payOrderDO.getRefundTimes() + 1)
                 .setRefundStatus(type);
-
         payOrderCoreMapper.updateById(updateOrderDO);
 
-        // 新退款订单
+        // 新退款订单
         PayRefundDO updateRefundDO = new PayRefundDO();
         updateRefundDO.setId(refundDO.getId())
                 .setSuccessTime(refundNotify.getRefundSuccessTime())
@@ -219,7 +227,7 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
                 .setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
         payRefundCoreMapper.updateById(updateRefundDO);
 
-        //插入退款通知记录
+        // 插入退款通知记录
         // TODO 通知商户成功或者失败. 现在通知似乎没有实现, 只是回调
         payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
                 .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refundDO.getId()).build());
@@ -235,12 +243,12 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
         if (!PayOrderStatusEnum.SUCCESS.getStatus().equals(order.getStatus())) {
             throw exception(PAY_ORDER_STATUS_IS_NOT_SUCCESS);
         }
-        //是否已经全额退款
+        // 是否已经全额退款
         if (PayRefundTypeEnum.ALL.getStatus().equals(order.getRefundStatus())) {
             throw exception(PAY_REFUND_ALL_REFUNDED);
         }
         // 校验金额 退款金额不能大于 原定的金额
-        if(req.getAmount() + order.getRefundAmount() > order.getAmount()){
+        if (req.getAmount() + order.getRefundAmount() > order.getAmount()){
             throw exception(PAY_REFUND_AMOUNT_EXCEED);
         }
         // 校验渠道订单号

+ 4 - 8
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedReqDTO.java

@@ -28,6 +28,7 @@ public class PayRefundUnifiedReqDTO {
      */
     private String userIp;
 
+    // TODO @jason:这个是否为非必传字段呀,只需要传递 payTradeNo 字段即可。尽可能精简
     /**
      * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 transaction_id
      * https://opendocs.alipay.com/apis alipay.trade.refund 中的 trade_no
@@ -35,7 +36,6 @@ public class PayRefundUnifiedReqDTO {
      */
     private String channelOrderNo;
 
-
     /**
      * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_trade_no
      * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no
@@ -43,24 +43,22 @@ public class PayRefundUnifiedReqDTO {
      */
     private String payTradeNo;
 
-
+    // TODO @jason:这个字段,要不就使用 merchantRefundId,更直接
     /**
      * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
-     * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
+     * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no
      * 退款请求单号  同一退款请求单号多次请求只退一笔。
      * 使用 商户的退款单号。{PayRefundDO 字段 merchantRefundNo}
      */
     @NotEmpty(message = "退款请求单号")
     private String refundReqNo;
 
-
     /**
      * 退款原因
      */
     @NotEmpty(message = "退款原因不能为空")
     private String reason;
 
-
     /**
      * 退款金额,单位:分
      */
@@ -68,12 +66,10 @@ public class PayRefundUnifiedReqDTO {
     @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
     private Long amount;
 
-
-
-
     /**
      * 退款结果 notify 回调地址, 支付宝退款不需要回调地址, 微信需要
      */
     @URL(message = "支付结果的 notify 回调地址必须是 URL 格式")
     private String notifyUrl;
+
 }

+ 3 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedRespDTO.java

@@ -17,11 +17,14 @@ import lombok.experimental.Accessors;
 @AllArgsConstructor
 @Data
 public class PayRefundUnifiedRespDTO {
+
+    // TODO @jason:可以合并下。退款处理中、成功,都是成功;其它就业务失败。这样,可以复用 PayCommonResult;这个 RespDTO 可以返回渠道的退款编号
     /**
      * 渠道的退款结果
      */
     private PayChannelRefundRespEnum channelResp;
 
+    // TODO @json:channelReturnCode 和 channelReturnMsg 放到 PayCommonResult 里噶
     /**
      * 渠道返回码
      */

+ 2 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelRefundRespEnum.java

@@ -11,6 +11,7 @@ import lombok.Getter;
 @Getter
 @AllArgsConstructor
 public enum PayChannelRefundRespEnum {
+
     SUCCESS(1, "退款成功"),
     FAILURE(2, "退款失败"),
     PROCESSING(3,"退款处理中"),
@@ -18,4 +19,5 @@ public enum PayChannelRefundRespEnum {
 
     private final Integer status;
     private final String name;
+
 }