Browse Source

重构退款逻辑,去掉退款后处理

jason 3 years ago
parent
commit
08103685f1
25 changed files with 232 additions and 718 deletions
  1. 5 0
      sql/change_db.sql
  2. 2 4
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/convert/order/PayRefundCoreConvert.java
  3. 18 4
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayRefundDO.java
  4. 4 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/order/PayRefundCoreMapper.java
  5. 2 1
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java
  6. 0 4
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayRefundStatusEnum.java
  7. 0 40
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundAbstractChannelPostHandler.java
  8. 0 26
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundChannelPostHandler.java
  9. 0 99
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/dto/PayRefundPostReqDTO.java
  10. 17 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/dto/PayRefundRespDTO.java
  11. 117 115
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundCoreServiceImpl.java
  12. 0 62
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/handler/PayRefundChannelFailedHandler.java
  13. 0 45
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/handler/PayRefundChannelNotifyHandler.java
  14. 0 56
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/handler/PayRefundChannelQueryHandler.java
  15. 0 50
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/handler/PayRefundChannelRetryHandler.java
  16. 0 64
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/handler/PayRefundChannelSuccessHandler.java
  17. 1 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedReqDTO.java
  18. 8 45
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedRespDTO.java
  19. 5 16
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
  20. 10 33
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClient.java
  21. 21 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelRefundRespEnum.java
  22. 0 51
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelRespEnum.java
  23. 4 2
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayRefundController.java
  24. 17 0
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundRespVO.java
  25. 1 1
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/order/PayRefundConvert.java

+ 5 - 0
sql/change_db.sql

@@ -0,0 +1,5 @@
+ALTER TABLE `ruoyi-vue-pro`.`pay_order_extension`
+CHANGE COLUMN `channel_notify_data` `channel_notify_data` VARCHAR(2048) CHARACTER SET 'utf8mb4' NULL DEFAULT NULL COMMENT '支付渠道异步通知的内容' ;
+
+ALTER TABLE `ruoyi-vue-pro`.`pay_refund`
+CHANGE COLUMN `req_no` `req_no` VARCHAR(64) NULL COMMENT '退款单请求号' ;

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

@@ -2,19 +2,17 @@ package cn.iocoder.yudao.coreservice.modules.pay.convert.order;
 
 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
-import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
-import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
 import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
 import org.mapstruct.Mapping;
 import org.mapstruct.Mappings;
+import org.mapstruct.factory.Mappers;
 
 @Mapper
 public interface PayRefundCoreConvert {
 
     PayRefundCoreConvert INSTANCE = Mappers.getMapper(PayRefundCoreConvert.class);
 
-    PayRefundPostReqDTO convert(PayRefundUnifiedRespDTO respDTO);
+
 
     //TODO 太多需要处理了, 暂时不用
     @Mappings(value = {

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

@@ -42,9 +42,10 @@ public class PayRefundDO extends BaseDO {
      * 注:针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更,
      * 防止该笔交易重复退款。支付宝会保证同样的退款请求号多次请求只会退一次。
      * 退款单请求号,根据规则生成
-      *
      * 例如说,R202109181134287570000
+      * 废弃,使用 merchantRefundNo 做退款请求号
      */
+    @Deprecated
     private String reqNo;
 
     /**
@@ -82,7 +83,7 @@ public class PayRefundDO extends BaseDO {
     /**
      * 交易订单号,根据规则生成
      * 调用支付渠道时,使用该字段作为对接的订单号。
-     * 1. 调用微信支付 https://api.mch.weixin.qq.com/pay/unifiedorder 时,使用该字段作为 out_trade_no
+     * 1. 调用微信支付 https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 时,使用该字段作为 out_trade_no
      * 2. 调用支付宝 https://opendocs.alipay.com/apis 时,使用该字段作为 out_trade_no
      *  这里对应 pay_extension 里面的 no
      * 例如说,P202110132239124200055
@@ -95,10 +96,20 @@ public class PayRefundDO extends BaseDO {
      * 商户订单编号
      */
     private String merchantOrderId;
+
     /**
-     * 商户退款订单号, 由商户系统产生, 由他们保证唯一,不能为空,通知商户时会传该字段。发送channel 使用 reqNo
+     * 商户退款订单号, 由商户系统产生, 由他们保证唯一,不能为空,通知商户时会传该字段。
      * 例如说,内部系统 A 的退款订单号。需要保证每个 PayMerchantDO 唯一
-     * TODO 芋艿:我理解 一个商户退款订单,可以对应多条退款记录, 因为有可能失败。但是 退款请求号 reqNo 必须唯一
+     * 个商户退款订单,对应一条退款请求记录。可多次提交。 渠道保持幂等
+     * 使用商户退款单,作为退款请求号
+     * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml 中的 out_refund_no
+     * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
+     * 退款请求号。
+     * 标识一次退款请求,需要保证在交易号下唯一,如需部分退款,则此参数必传。
+     * 注:针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更,
+     * 防止该笔交易重复退款。支付宝会保证同样的退款请求号多次请求只会退一次。
+     * 退款单请求号,根据规则生成
+     * 例如说,R202109181134287570000
      *
      */
     private String merchantRefundNo;
@@ -151,6 +162,9 @@ public class PayRefundDO extends BaseDO {
      */
     private String channelOrderNo;
     /**
+     * 微信中的 refund_id
+     * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml
+     * 支付宝没有
      * 渠道退款单号,渠道返回
      */
     private String channelRefundNo;

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

@@ -15,4 +15,8 @@ public interface PayRefundCoreMapper extends BaseMapperX<PayRefundDO> {
     default PayRefundDO selectByReqNo(String reqNo) {
         return selectOne("req_no", reqNo);
     }
+
+    default  PayRefundDO selectByTradeNoAndMerchantRefundNo(String tradeNo, String merchantRefundNo){
+        return  selectOne("trade_no", tradeNo, "merchant_refund_no", merchantRefundNo);
+    }
 }

+ 2 - 1
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java

@@ -46,9 +46,10 @@ public interface PayErrorCodeCoreConstants {
     ErrorCode PAY_REFUND_AMOUNT_EXCEED = new ErrorCode(1007006000, "退款金额超过订单可退款金额");
     ErrorCode PAY_REFUND_ALL_REFUNDED = new ErrorCode(1007006001, "订单已经全额退款");
     ErrorCode PAY_REFUND_CHN_ORDER_NO_IS_NULL = new ErrorCode(1007006002, "该订单的渠道订单为空");
-    ErrorCode PAY_REFUND_POST_HANDLER_NOT_FOUND = new ErrorCode(1007006003, "未找到对应的退款后置处理类");
+    ErrorCode PAY_REFUND_SUCCEED = new ErrorCode(1007006003, "已经退款成功");
     ErrorCode PAY_REFUND_NOT_FOUND = new ErrorCode(1007006004, "支付退款单不存在");
 
+
     /**
      * ========== 支付商户信息 1-007-004-000 ==========
      */

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

@@ -9,10 +9,6 @@ public enum PayRefundStatusEnum {
     CREATE(0, "退款订单生成"),
     SUCCESS(1, "退款成功"),
     FAILURE(2, "退款失败"),
-    PROCESSING_NOTIFY(3,"退款中, 渠道通知结果"),
-    PROCESSING_QUERY(4,"退款中, 系统查询结果"),
-    UNKNOWN_RETRY(5,"状态未知,需要重试"),
-    UNKNOWN_QUERY(6,"状态未知,系统查询结果"),
     CLOSE(99, "退款关闭");
 
     private final Integer status;

+ 0 - 40
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundAbstractChannelPostHandler.java

@@ -1,40 +0,0 @@
-package cn.iocoder.yudao.coreservice.modules.pay.service.order;
-
-import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
-import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
-import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
-import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundCoreMapper;
-
-/**
- * 支付退款订单渠道返回后 , 后置处理抽象类, 处理公用的逻辑
- * @author  jason
- */
-public abstract  class PayRefundAbstractChannelPostHandler implements PayRefundChannelPostHandler {
-
-    private final PayOrderCoreMapper payOrderCoreMapper;
-    private final PayRefundCoreMapper payRefundMapper;
-
-    public PayRefundAbstractChannelPostHandler(PayOrderCoreMapper payOrderCoreMapper,
-                                               PayRefundCoreMapper payRefundMapper){
-        this.payOrderCoreMapper = payOrderCoreMapper;
-        this.payRefundMapper = payRefundMapper;
-    }
-
-
-    /**
-     * 更新退款单
-     * @param refundDO  需要更新的退款单信息
-     */
-    protected void updatePayRefund(PayRefundDO refundDO){
-        payRefundMapper.updateById(refundDO);
-    }
-
-
-    /**
-     * 更新原始支付订单
-     * @param payOrderDO 支付订单信息
-     */
-    protected void updatePayOrder(PayOrderDO payOrderDO){
-        payOrderCoreMapper.updateById(payOrderDO);
-    }
-}

+ 0 - 26
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundChannelPostHandler.java

@@ -1,26 +0,0 @@
-package cn.iocoder.yudao.coreservice.modules.pay.service.order;
-
-import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
-import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
-
-/**
- * 支付退款订单 ,渠道返回后 后置处理
- *
- * @author jason
- */
-public interface PayRefundChannelPostHandler {
-
-    /**
-     * 支持的渠道返回值
-     *
-     * @return 支持的渠道返回值数组
-     */
-    PayChannelRespEnum[] supportHandleResp();
-
-    /**
-     * 根据渠道返回,处理支付退款单
-     *
-     * @param req 退款后置处理请求
-     */
-    void handleRefundChannelResp(PayRefundPostReqDTO req);
-}

+ 0 - 99
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/dto/PayRefundPostReqDTO.java

@@ -1,99 +0,0 @@
-package cn.iocoder.yudao.coreservice.modules.pay.service.order.dto;
-
-import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum;
-import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import lombok.experimental.Accessors;
-
-/**
- * 退款后置处理请求 Request DTO
- */
-@Data
-@Accessors(chain = true)
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class PayRefundPostReqDTO {
-
-
-    /**
-     * 渠道的通用返回结果
-     */
-    private PayChannelRespEnum respEnum;
-
-
-
-    private PayRefundTypeEnum refundTypeEnum;
-
-    /**
-     * 已退款的总金额
-     */
-    private Long  refundedAmount;
-
-    /**
-     * 本次退款金额
-     */
-    private Long refundAmount;
-
-   /**
-     * 已退款次数
-     */
-    private Integer refundedTimes;
-
-
-    /**
-     * 订单编号
-     */
-    private Long  orderId;
-
-    /**
-     * 退款单编号
-     */
-    private Long refundId;
-
-
-    /**
-     * 渠道退款单号
-     */
-    private String channelRefundNo;
-
-
-    /**
-     * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_trade_no
-     * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no
-     * 支付交易号 {PayOrderExtensionDO no字段} 和 渠道订单号 不能同时为空
-     */
-    private String payTradeNo;
-
-
-    /**
-     * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
-     * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
-     * 退款请求单号  同一退款请求单号多次请求只退一笔。
-     */
-    private String refundReqNo;
-
-
-
-    /**
-     * 调用异常错误信息
-     */
-    private String exceptionMsg;
-
-
-    /**
-     * 渠道的错误码
-     */
-    private String channelErrCode;
-
-
-    /**
-     * 渠道的错误描述
-     */
-    private String channelErrMsg;
-
-
-}

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

@@ -16,6 +16,23 @@ import lombok.experimental.Accessors;
 @AllArgsConstructor
 public class PayRefundRespDTO {
 
+    /**
+     * 渠道返回结果
+     * 退款处理中和退款成功  返回  1
+     * 失败和其他情况 返回 2
+     */
+    private Integer channelReturnResult;
+
+    /**
+     * 渠道返回code
+     */
+    private String channelReturnCode;
+
+    /**
+     * 渠道返回消息
+     */
+    private String  channelReturnMsg;
+
     /**
      * 支付退款单编号, 自增
      */

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

@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl;
 
 import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.coreservice.modules.pay.convert.order.PayRefundCoreConvert;
 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.order.PayOrderDO;
@@ -14,7 +13,6 @@ 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.PayRefundPostReqDTO;
 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;
@@ -23,21 +21,18 @@ import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
 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.order.PayRefundCoreService;
-import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundChannelPostHandler;
 import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundRespDTO;
-import cn.iocoder.yudao.coreservice.modules.pay.util.PaySeqUtils;
 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.PayRefundUnifiedReqDTO;
 import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum;
 import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
-import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import java.util.*;
 
@@ -65,25 +60,6 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
     @Resource
     private PayClientFactory payClientFactory;
 
-    /**
-     * 处理渠道返回结果的后置处理器 集合
-     */
-    @Resource
-    private List<PayRefundChannelPostHandler> handlerList;
-
-    private final EnumMap<PayChannelRespEnum, PayRefundChannelPostHandler> mapHandlers = new EnumMap<>(PayChannelRespEnum.class);
-
-    @PostConstruct
-    public void init(){
-        if (Objects.nonNull(handlerList)) {
-            handlerList.forEach(handler -> {
-                for (PayChannelRespEnum item : handler.supportHandleResp()) {
-                    mapHandlers.put(item, handler);
-                }
-            });
-        }
-    }
-
     @Override
     @Transactional(rollbackFor = Exception.class)
     public PayRefundRespDTO submitRefundOrder(PayRefundReqDTO req) {
@@ -106,66 +82,89 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
 
         // 校验退款的条件
         validatePayRefund(req, order);
-
         // 退款类型
         PayRefundTypeEnum refundType = PayRefundTypeEnum.SOME;
         if (Objects.equals(req.getAmount(), order.getAmount())) {
             refundType = PayRefundTypeEnum.ALL;
         }
-        // 退款单入库 退款单状态:生成,  没有和渠道产生交互
+
         PayOrderExtensionDO orderExtensionDO = payOrderExtensionCoreMapper.selectById(order.getSuccessExtensionId());
-        PayRefundDO refundDO = PayRefundDO.builder().channelOrderNo(order.getChannelOrderNo())
-                .appId(order.getAppId())
-                .channelOrderNo(order.getChannelOrderNo())
-                .channelCode(order.getChannelCode())
-                .channelId(order.getChannelId())
-                .merchantId(order.getMerchantId())
-                .orderId(order.getId())
-                .merchantRefundNo(req.getMerchantRefundNo())
-                .notifyUrl(app.getRefundNotifyUrl())
-                .payAmount(order.getAmount())
-                .refundAmount(req.getAmount())
-                .userIp(req.getUserIp())
-                .merchantOrderId(order.getMerchantOrderId())
-                .tradeNo(orderExtensionDO.getNo())
-                .status(PayRefundStatusEnum.CREATE.getStatus())
-                .reason(req.getReason())
-                .notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus())
-                .reqNo(PaySeqUtils.genRefundReqNo())
-                .type(refundType.getStatus())
-                .build();
-         payRefundCoreMapper.insert(refundDO);
-
-        // 调用渠道进行退款
-         PayRefundUnifiedReqDTO unifiedReqDTO = PayRefundUnifiedReqDTO.builder()
-                .userIp(req.getUserIp())
-                .channelOrderNo(refundDO.getChannelOrderNo())
-                .payTradeNo(refundDO.getTradeNo())
-                .refundReqNo(refundDO.getReqNo())
-                .amount(req.getAmount())
-                .reason(refundDO.getReason())
-                .build();
-         PayRefundUnifiedRespDTO refundUnifiedRespDTO = client.unifiedRefund(unifiedReqDTO);
-         // 根据渠道返回,获取退款后置处理,由postHandler 进行处理
-         PayRefundChannelPostHandler payRefundChannelPostHandler = mapHandlers.get(refundUnifiedRespDTO.getRespEnum());
-         if (Objects.isNull(payRefundChannelPostHandler)) {
-             throw exception(PAY_REFUND_POST_HANDLER_NOT_FOUND);
-         }
-         PayRefundPostReqDTO dto = PayRefundCoreConvert.INSTANCE.convert(refundUnifiedRespDTO);
-         dto.setRefundAmount(req.getAmount())
-            .setRefundedAmount(order.getRefundAmount())
-            .setRefundedTimes(order.getRefundTimes())
-            .setRefundId(refundDO.getId())
-            .setOrderId(order.getId())
-            .setRefundTypeEnum(refundType);
-         //调用退款的后置处理
-         payRefundChannelPostHandler.handleRefundChannelResp(dto);
-
-         return PayRefundRespDTO.builder().refundId(refundDO.getId()).build();
+        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())){
+                //已成功退款
+               throw exception(PAY_REFUND_SUCCEED);
+            }else{
+                //保证商户退款单不变,重复向渠道发起退款。渠道保持幂等
+                unifiedReqDTO.setUserIp(req.getUserIp())
+                             .setAmount(payRefundDO.getRefundAmount())
+                             .setChannelOrderNo(payRefundDO.getChannelOrderNo())
+                             .setPayTradeNo(payRefundDO.getTradeNo())
+                             .setRefundReqNo(payRefundDO.getMerchantRefundNo())
+                             .setReason(payRefundDO.getReason());
+            }
+        }else{
+            //新生成退款单。 退款单入库 退款单状态:生成
+            payRefundDO = PayRefundDO.builder().channelOrderNo(order.getChannelOrderNo())
+                    .appId(order.getAppId())
+                    .channelOrderNo(order.getChannelOrderNo())
+                    .channelCode(order.getChannelCode())
+                    .channelId(order.getChannelId())
+                    .merchantId(order.getMerchantId())
+                    .orderId(order.getId())
+                    .merchantRefundNo(req.getMerchantRefundNo())
+                    .notifyUrl(app.getRefundNotifyUrl())
+                    .payAmount(order.getAmount())
+                    .refundAmount(req.getAmount())
+                    .userIp(req.getUserIp())
+                    .merchantOrderId(order.getMerchantOrderId())
+                    .tradeNo(orderExtensionDO.getNo())
+                    .status(PayRefundStatusEnum.CREATE.getStatus())
+                    .reason(req.getReason())
+                    .notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus())
+                    .type(refundType.getStatus())
+                    .build();
+            payRefundCoreMapper.insert(payRefundDO);
+            unifiedReqDTO.setUserIp(req.getUserIp())
+                    .setAmount(payRefundDO.getRefundAmount())
+                    .setChannelOrderNo(payRefundDO.getChannelOrderNo())
+                    .setPayTradeNo(payRefundDO.getTradeNo())
+                    .setRefundReqNo(payRefundDO.getMerchantRefundNo())
+                    .setReason(req.getReason());
+        }
+        //向渠道发起退款申请
+        PayRefundUnifiedRespDTO refundUnifiedRespDTO = client.unifiedRefund(unifiedReqDTO);
+        //构造退款申请返回对象
+        PayRefundRespDTO respDTO = new PayRefundRespDTO();
+        if(refundUnifiedRespDTO.getChannelResp() == PayChannelRefundRespEnum.SUCCESS
+            ||refundUnifiedRespDTO.getChannelResp() == PayChannelRefundRespEnum.PROCESSING ){
+            //成功处理, 在退款通知中处理, 这里不处理
+            respDTO.setChannelReturnResult(PayChannelRefundRespEnum.SUCCESS.getStatus());
+            respDTO.setRefundId(payRefundDO.getId());
+        }else {
+            //失败返回错误给前端,可以重新发起退款,保证退款请求号(这里是商户退款单号), 避免重复退款。
+            respDTO.setChannelReturnResult(PayChannelRefundRespEnum.FAILURE.getStatus());
+            //更新退款单状态
+            PayRefundDO updatePayRefund = new PayRefundDO();
+            updatePayRefund.setId(payRefundDO.getId())
+                    .setChannelErrorMsg(refundUnifiedRespDTO.getChannelMsg())
+                    .setChannelErrorCode(refundUnifiedRespDTO.getChannelCode())
+                    .setStatus(PayRefundStatusEnum.FAILURE.getStatus());
+            payRefundCoreMapper.updateById(updatePayRefund);
+        }
+        respDTO.setChannelReturnCode(refundUnifiedRespDTO.getChannelCode())
+                .setChannelReturnMsg(refundUnifiedRespDTO.getChannelMsg());
+        return respDTO;
     }
 
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) {
         log.info("[notifyPayRefund][channelId({}) 回调数据({})]", channelId, notifyData.getBody());
         // 校验支付渠道是否有效
@@ -178,49 +177,52 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
         }
         // 解析渠道退款通知数据, 统一处理
         PayRefundNotifyDTO refundNotify = client.parseRefundNotify(notifyData);
-
-        // TODO @jason:抽一个 notifyPayRefundSuccess 方法
         if (Objects.equals(PayNotifyRefundStatusEnum.SUCCESS,refundNotify.getStatus())){
-            // 退款成功。 支付宝只有退款成功才会发通知
-            PayRefundDO refundDO = payRefundCoreMapper.selectByReqNo(refundNotify.getReqNo());
-            if (refundDO == null) {
-                log.error("不存在 seqNo 为{} 的支付退款单",refundNotify.getReqNo());
-                throw exception(PAY_REFUND_NOT_FOUND);
-            }
-            Long refundAmount = refundDO.getRefundAmount();
-            Integer type = refundDO.getType();
-            PayOrderStatusEnum orderStatus = PayOrderStatusEnum.SUCCESS;
-            if(PayRefundTypeEnum.ALL.getStatus().equals(type)){
-                orderStatus = PayOrderStatusEnum.CLOSED;
-            }
-            // 更新支付订单
-            PayOrderDO payOrderDO = payOrderCoreMapper.selectById(refundDO.getOrderId());
-            // 需更新已退金额
-            Long refundedAmount = payOrderDO.getRefundAmount();
-            PayOrderDO updateOrderDO = new PayOrderDO();
-            updateOrderDO.setId(refundDO.getOrderId())
-                    .setRefundAmount(refundedAmount + refundAmount)
-                    .setStatus(orderStatus.getStatus())
-                    .setRefundStatus(type);
-            payOrderCoreMapper.updateById(updateOrderDO);
-
-            // 跟新退款订单
-            PayRefundDO updateRefundDO = new PayRefundDO();
-            updateRefundDO.setId(refundDO.getId())
-                    .setSuccessTime(refundNotify.getRefundSuccessTime())
-                    .setChannelRefundNo(refundNotify.getChannelOrderNo())
-                    .setTradeNo(refundNotify.getTradeNo())
-                    .setNotifyTime(new Date())
-                    .setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
-            payRefundCoreMapper.updateById(updateRefundDO);
-
-            //插入退款通知记录
-            // TODO 通知商户成功或者失败. 现在通知似乎没有实现, 只是回调
-            payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
-                    .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refundDO.getId()).build());
+            payRefundSuccess(refundNotify);
         } else {
-            //TODO 退款失败
+            //TODO 支付异常, 支付宝似乎没有支付异常的通知。
+        }
+    }
+
+    private void payRefundSuccess(PayRefundNotifyDTO refundNotify) {
+        PayRefundDO refundDO = payRefundCoreMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(), refundNotify.getReqNo());
+        if (refundDO == null) {
+            log.error("不存在 seqNo 为{} 的支付退款单",refundNotify.getReqNo());
+            throw exception(PAY_REFUND_NOT_FOUND);
+        }
+        Long refundAmount = refundDO.getRefundAmount();
+        Integer type = refundDO.getType();
+        PayOrderStatusEnum orderStatus = PayOrderStatusEnum.SUCCESS;
+        if(PayRefundTypeEnum.ALL.getStatus().equals(type)){
+            orderStatus = PayOrderStatusEnum.CLOSED;
         }
+        // 更新支付订单
+        PayOrderDO payOrderDO = payOrderCoreMapper.selectById(refundDO.getOrderId());
+        // 需更新已退金额
+        Long refundedAmount = payOrderDO.getRefundAmount();
+        PayOrderDO updateOrderDO = new PayOrderDO();
+        updateOrderDO.setId(refundDO.getOrderId())
+                .setRefundAmount(refundedAmount + refundAmount)
+                .setStatus(orderStatus.getStatus())
+                .setRefundTimes(payOrderDO.getRefundTimes()+1)
+                .setRefundStatus(type);
+
+        payOrderCoreMapper.updateById(updateOrderDO);
+
+        // 跟新退款订单
+        PayRefundDO updateRefundDO = new PayRefundDO();
+        updateRefundDO.setId(refundDO.getId())
+                .setSuccessTime(refundNotify.getRefundSuccessTime())
+                .setChannelRefundNo(refundNotify.getChannelOrderNo())
+                .setTradeNo(refundNotify.getTradeNo())
+                .setNotifyTime(new Date())
+                .setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
+        payRefundCoreMapper.updateById(updateRefundDO);
+
+        //插入退款通知记录
+        // TODO 通知商户成功或者失败. 现在通知似乎没有实现, 只是回调
+        payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
+                .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refundDO.getId()).build());
     }
 
     /**

+ 0 - 62
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/handler/PayRefundChannelFailedHandler.java

@@ -1,62 +0,0 @@
-package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl.handler;
-
-import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
-import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
-import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
-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.PayRefundStatusEnum;
-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.PayRefundAbstractChannelPostHandler;
-import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
-import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Resource;
-import java.util.Optional;
-
-/**
- * 支付退款订单渠道返回失败的后置处理类
- * {@link PayChannelRespEnum#CALL_EXCEPTION}
- * {@link PayChannelRespEnum#CAN_NOT_RETRY_FAILURE}
- */
-@Service
-public class PayRefundChannelFailedHandler extends PayRefundAbstractChannelPostHandler {
-
-    @Resource
-    private PayNotifyCoreService payNotifyCoreService;
-
-    public PayRefundChannelFailedHandler(PayOrderCoreMapper payOrderCoreMapper, PayRefundCoreMapper payRefundMapper) {
-        super(payOrderCoreMapper, payRefundMapper);
-    }
-
-    @Override
-    public PayChannelRespEnum[] supportHandleResp() {
-        return new PayChannelRespEnum[] {PayChannelRespEnum.CALL_EXCEPTION, PayChannelRespEnum.CAN_NOT_RETRY_FAILURE};
-    }
-
-
-    @Override
-    public void handleRefundChannelResp(PayRefundPostReqDTO req) {
-        //退款失败
-        //更新退款单表
-        PayRefundDO updateRefundDO = new PayRefundDO();
-
-        updateRefundDO.setId(req.getRefundId())
-                .setStatus(PayRefundStatusEnum.FAILURE.getStatus())
-                .setChannelErrorCode(req.getChannelErrCode())
-                .setChannelErrorMsg(Optional.ofNullable(req.getChannelErrMsg())
-                                          .orElse(req.getExceptionMsg()));
-        updatePayRefund(updateRefundDO);
-        PayOrderDO updateOrderDO = new PayOrderDO();
-        //更新订单表
-        updateOrderDO.setId(req.getOrderId())
-                .setRefundTimes(req.getRefundedTimes() + 1);
-        updatePayOrder(updateOrderDO);
-        // 立刻插入退款通知记录
-        // TODO 通知商户成功或者失败. 现在通知似乎没有实现, 只是回调
-        payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
-                .type(PayNotifyTypeEnum.REFUND.getType()).dataId(req.getRefundId()).build());
-    }
-}

+ 0 - 45
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/handler/PayRefundChannelNotifyHandler.java

@@ -1,45 +0,0 @@
-package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl.handler;
-
-import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
-import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
-import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
-import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundCoreMapper;
-import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
-import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler;
-import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
-import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
-import org.springframework.stereotype.Service;
-
-/**
- * 支付退款订单渠道返回通知 {@link PayChannelRespEnum#PROCESSING_NOTIFY},后置处理类
- * 支付宝退款单好像没有回调, 微信会触发回调
- */
-@Service
-public class PayRefundChannelNotifyHandler extends PayRefundAbstractChannelPostHandler {
-
-    public PayRefundChannelNotifyHandler(PayOrderCoreMapper payOrderCoreMapper,
-                                         PayRefundCoreMapper payRefundMapper) {
-        super(payOrderCoreMapper, payRefundMapper);
-    }
-
-    @Override
-    public PayChannelRespEnum[] supportHandleResp() {
-        return new PayChannelRespEnum[] {PayChannelRespEnum.PROCESSING_NOTIFY};
-    }
-
-    @Override
-    public void handleRefundChannelResp(PayRefundPostReqDTO req) {
-        PayRefundDO updateRefundDO = new PayRefundDO();
-        //更新退款单表
-        updateRefundDO.setId(req.getRefundId())
-                .setStatus(PayRefundStatusEnum.PROCESSING_NOTIFY.getStatus());
-        updatePayRefund(updateRefundDO);
-
-        PayOrderDO updateOrderDO = new PayOrderDO();
-        //更新订单表
-        updateOrderDO.setId(req.getOrderId())
-                .setRefundTimes(req.getRefundedTimes() + 1);
-        updatePayOrder(updateOrderDO);
-
-    }
-}

+ 0 - 56
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/handler/PayRefundChannelQueryHandler.java

@@ -1,56 +0,0 @@
-package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl.handler;
-
-import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
-import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
-import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
-import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundCoreMapper;
-import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
-import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler;
-import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
-import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
-import org.springframework.stereotype.Service;
-
-import java.util.Objects;
-
-/**
- * 支付退款订单渠道返回需调用查询接口的后置处理类
- * {@link PayChannelRespEnum#PROCESSING_QUERY} //TODO 芋道源码 是不是微信有这样的情况
- * {@link PayChannelRespEnum#READ_TIME_OUT_EXCEPTION}
- */
-@Service
-public class PayRefundChannelQueryHandler extends PayRefundAbstractChannelPostHandler {
-
-
-    public PayRefundChannelQueryHandler(PayOrderCoreMapper payOrderCoreMapper,
-                                        PayRefundCoreMapper payRefundMapper) {
-        super(payOrderCoreMapper, payRefundMapper);
-    }
-
-    @Override
-    public PayChannelRespEnum[] supportHandleResp() {
-        return new PayChannelRespEnum[]{PayChannelRespEnum.PROCESSING_QUERY, PayChannelRespEnum.READ_TIME_OUT_EXCEPTION};
-    }
-
-    @Override
-    public void handleRefundChannelResp(PayRefundPostReqDTO req) {
-        final PayChannelRespEnum respEnum = req.getRespEnum();
-        PayRefundStatusEnum refundStatus =
-                Objects.equals(PayChannelRespEnum.PROCESSING_QUERY, respEnum) ? PayRefundStatusEnum.PROCESSING_QUERY
-                        : PayRefundStatusEnum.UNKNOWN_QUERY;
-        //更新退款单表
-        PayRefundDO updateRefundDO = new PayRefundDO();
-        updateRefundDO.setId(req.getRefundId())
-                .setStatus(refundStatus.getStatus())
-                .setChannelErrorCode(req.getChannelErrCode())
-                .setChannelErrorMsg(req.getChannelErrMsg());
-        updatePayRefund(updateRefundDO);
-
-        PayOrderDO updateOrderDO = new PayOrderDO();
-        //更新订单表
-        updateOrderDO.setId(req.getOrderId())
-                .setRefundTimes(req.getRefundedTimes() + 1);
-        updatePayOrder(updateOrderDO);
-
-        //TODO 发起查询任务
-    }
-}

+ 0 - 50
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/handler/PayRefundChannelRetryHandler.java

@@ -1,50 +0,0 @@
-package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl.handler;
-
-import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
-import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
-import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
-import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundCoreMapper;
-import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
-import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler;
-import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
-import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
-import org.springframework.stereotype.Service;
-
-/**
- * 支付退款订单渠道返回重试的后置处理类
- * {@link PayChannelRespEnum#RETRY_FAILURE}
- */
-@Service
-public class PayRefundChannelRetryHandler extends PayRefundAbstractChannelPostHandler {
-
-
-    public PayRefundChannelRetryHandler(PayOrderCoreMapper payOrderCoreMapper,
-                                        PayRefundCoreMapper payRefundMapper) {
-        super(payOrderCoreMapper, payRefundMapper);
-    }
-
-    @Override
-    public PayChannelRespEnum[] supportHandleResp() {
-        return new PayChannelRespEnum[] {PayChannelRespEnum.RETRY_FAILURE};
-    }
-
-    @Override
-    public void handleRefundChannelResp(PayRefundPostReqDTO req) {
-
-        PayRefundDO updateRefundDO = new PayRefundDO();
-        //更新退款单表
-        updateRefundDO.setId(req.getRefundId())
-                .setStatus(PayRefundStatusEnum.UNKNOWN_RETRY.getStatus())
-                .setChannelErrorCode(req.getChannelErrCode())
-                .setChannelErrorMsg(req.getChannelErrMsg());
-        updatePayRefund(updateRefundDO);
-
-        PayOrderDO updateOrderDO = new PayOrderDO();
-        //更新订单表
-        updateOrderDO.setId(req.getOrderId())
-                .setRefundTimes(req.getRefundedTimes() + 1);
-        updatePayOrder(updateOrderDO);
-
-        //TODO 发起重试任务
-    }
-}

+ 0 - 64
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/handler/PayRefundChannelSuccessHandler.java

@@ -1,64 +0,0 @@
-package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl.handler;
-
-import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
-import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
-import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
-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.PayRefundStatusEnum;
-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.PayRefundAbstractChannelPostHandler;
-import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
-import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Resource;
-import java.util.Date;
-/**
- * 支付退款订单渠道返回退款成功的后置处理类
- * {@link PayChannelRespEnum#SYNC_SUCCESS}
- */
-@Service
-public class PayRefundChannelSuccessHandler extends PayRefundAbstractChannelPostHandler {
-
-
-    @Resource
-    private PayNotifyCoreService payNotifyCoreService;
-
-
-    public PayRefundChannelSuccessHandler(PayOrderCoreMapper payOrderCoreMapper,
-                                          PayRefundCoreMapper payRefundMapper) {
-        super(payOrderCoreMapper, payRefundMapper);
-    }
-
-    @Override
-    public PayChannelRespEnum[] supportHandleResp() {
-        return new PayChannelRespEnum[]{PayChannelRespEnum.SYNC_SUCCESS};
-    }
-
-    @Override
-    public void handleRefundChannelResp(PayRefundPostReqDTO req) {
-        //退款成功
-        PayRefundDO updateRefundDO = new PayRefundDO();
-        //更新退款单表
-        updateRefundDO.setId(req.getRefundId())
-                .setStatus(PayRefundStatusEnum.SUCCESS.getStatus())
-                .setChannelRefundNo(req.getChannelRefundNo())
-                .setSuccessTime(new Date());
-        updatePayRefund(updateRefundDO);
-
-        PayOrderDO updateOrderDO = new PayOrderDO();
-        //更新订单表
-        updateOrderDO.setId(req.getOrderId())
-                .setRefundTimes(req.getRefundedTimes() + 1)
-                .setRefundStatus(req.getRefundTypeEnum().getStatus())
-                .setRefundAmount(req.getRefundedAmount()+ req.getRefundAmount());
-         updatePayOrder(updateOrderDO);
-
-         // 立刻插入退款通知记录
-        // TODO 通知商户成功或者失败. 现在通知似乎没有实现, 只是回调
-        payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
-                .type(PayNotifyTypeEnum.REFUND.getType()).dataId(req.getRefundId()).build());
-    }
-}

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

@@ -48,6 +48,7 @@ public class PayRefundUnifiedReqDTO {
      * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
      * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
      * 退款请求单号  同一退款请求单号多次请求只退一笔。
+     * 使用 商户的退款单号。{PayRefundDO 字段 merchantRefundNo}
      */
     @NotEmpty(message = "退款请求单号")
     private String refundReqNo;

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

@@ -1,15 +1,13 @@
 package cn.iocoder.yudao.framework.pay.core.client.dto;
 
-import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
-
-import javax.validation.constraints.NotEmpty;
 /**
- * 统一 退款 Response DTO
+ * 统一退款 Response DTO
  *
  * @author jason
  */
@@ -19,55 +17,20 @@ import javax.validation.constraints.NotEmpty;
 @AllArgsConstructor
 @Data
 public class PayRefundUnifiedRespDTO {
-
-
-
-    /**
-     * 渠道的通用返回结果
-     */
-    private PayChannelRespEnum respEnum;
-
-
-
-    /**
-     * 渠道退款单号
-     */
-    private String channelRefundNo;
-
-
-    /**
-     * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_trade_no
-     * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no
-     * 支付交易号 {PayOrderExtensionDO no字段} 和 渠道订单号 不能同时为空
-     */
-    private String payTradeNo;
-
-
-    /**
-     * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
-     * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
-     * 退款请求单号  同一退款请求单号多次请求只退一笔。
-     */
-    private String refundReqNo;
-
-
-
     /**
-     * 调用异常错误信息
+     * 渠道的退款结果
      */
-    private String exceptionMsg;
-
+    private PayChannelRefundRespEnum channelResp;
 
     /**
-     * 渠道的错误
+     * 渠道返回码
      */
-    private String channelErrCode;
-
+    private String channelCode;
 
     /**
-     * 渠道的错误描述
+     * 渠道返回信息
      */
-    private String channelErrMsg;
+    private String channelMsg;
 
     //TODO 退款资金渠 ???
 }

+ 5 - 16
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.framework.pay.core.client.impl;
 
 import cn.hutool.extra.validation.ValidationUtil;
+import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
 import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
@@ -8,11 +9,9 @@ import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
 import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
 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.PayChannelRespEnum;
 import lombok.extern.slf4j.Slf4j;
 
-import java.net.SocketTimeoutException;
-
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 
 /**
@@ -108,20 +107,10 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
         PayRefundUnifiedRespDTO resp;
         try {
             resp = doUnifiedRefund(reqDTO);
-        } catch (SocketTimeoutException ex){
-            // 网络 read time out 异常
-            log.error("[unifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), ex);
-            return PayRefundUnifiedRespDTO.builder()
-                    .exceptionMsg(ex.getMessage())
-                    .respEnum(PayChannelRespEnum.READ_TIME_OUT_EXCEPTION)
-                    .build();
-        } catch (Throwable ex) {
-            // 打印异常日志
+        }  catch (Throwable ex) {
+            // 记录异常日志
             log.error("[unifiedRefund][request({}) 发起退款失败]", toJsonString(reqDTO), ex);
-            return PayRefundUnifiedRespDTO.builder()
-                    .exceptionMsg(ex.getMessage())
-                    .respEnum(PayChannelRespEnum.CALL_EXCEPTION)
-                    .build();
+            throw exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR);
         }
         return resp;
     }

+ 10 - 33
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClient.java

@@ -5,7 +5,7 @@ import cn.hutool.core.date.DateUtil;
 import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
 import cn.iocoder.yudao.framework.pay.core.client.dto.*;
 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
-import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum;
 import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
 import com.alipay.api.AlipayApiException;
 import com.alipay.api.AlipayConfig;
@@ -17,7 +17,6 @@ import com.alipay.api.response.AlipayTradeRefundResponse;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 
-import java.net.SocketTimeoutException;
 import java.nio.charset.StandardCharsets;
 import java.util.Map;
 
@@ -118,43 +117,21 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
                 //退款成功,更新为PROCESSING_NOTIFY, 而不是 SYNC_SUCCESS 通过支付宝回调接口处理。退款导致触发的异步通知,
                 //退款导致触发的异步通知是发送到支付接口中设置的notify_url
                 //TODO 沙箱环境 返回 的tradeNo(渠道退款单号) 和 订单的tradNo 是一个值,是不是理解不对?
-                respDTO.setRespEnum(PayChannelRespEnum.PROCESSING_NOTIFY);
+                respDTO.setChannelResp(PayChannelRefundRespEnum.SUCCESS)
+                        .setChannelCode(response.getCode())
+                        .setChannelMsg(response.getMsg());
             }else{
-                //特殊处理 sub_code  ACQ.SYSTEM_ERROR(系统错误), 需要调用重试任务
-                //沙箱环境返回的貌似是”aop.ACQ.SYSTEM_ERROR“, 用contain
-                if (response.getSubCode().contains("ACQ.SYSTEM_ERROR")) {
-                    respDTO.setRespEnum(PayChannelRespEnum.RETRY_FAILURE)
-                            .setChannelErrMsg(response.getSubMsg())
-                            .setChannelErrCode(response.getSubCode());
-                }else{
-                    //交易已关闭,需要查询确认退款是否已经完成
-                    if("ACQ.TRADE_HAS_CLOSE".equals(response.getSubCode())){
-                        respDTO.setRespEnum(PayChannelRespEnum.PROCESSING_QUERY)
-                                .setChannelErrMsg(response.getSubMsg())
-                                .setChannelErrCode(response.getSubCode());
-                    }else {
-                        //其他当做不可以重试的错误
-                        respDTO.setRespEnum(PayChannelRespEnum.CAN_NOT_RETRY_FAILURE)
-                                .setChannelErrCode(response.getSubCode())
-                                .setChannelErrMsg(response.getSubMsg());
-                    }
-                }
+                respDTO.setChannelResp(PayChannelRefundRespEnum.FAILURE)
+                        .setChannelCode(response.getSubCode())
+                        .setChannelMsg(response.getSubMsg());
             }
             return respDTO;
         } catch (AlipayApiException e) {
             //TODO 记录异常日志
             log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), e);
-            Throwable cause = e.getCause();
-            //网络 read time out 异常, 退款状态未知
-            if (cause instanceof SocketTimeoutException) {
-                respDTO.setExceptionMsg(e.getMessage())
-                        .setRespEnum(PayChannelRespEnum.READ_TIME_OUT_EXCEPTION);
-            }else{
-                respDTO.setExceptionMsg(e.getMessage())
-                        .setChannelErrCode(e.getErrCode())
-                        .setChannelErrMsg(e.getErrMsg())
-                        .setRespEnum(PayChannelRespEnum.CALL_EXCEPTION);
-            }
+            respDTO.setChannelResp(PayChannelRefundRespEnum.FAILURE)
+                    .setChannelCode(e.getErrCode())
+                    .setChannelMsg(e.getErrMsg());
             return respDTO;
         }
     }

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

@@ -0,0 +1,21 @@
+package cn.iocoder.yudao.framework.pay.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 渠道统一的退款返回结果
+ *
+ * @author  jason
+ */
+@Getter
+@AllArgsConstructor
+public enum PayChannelRefundRespEnum {
+    SUCCESS(1, "退款成功"),
+    FAILURE(2, "退款失败"),
+    PROCESSING(3,"退款处理中"),
+    CLOSED(4, "退款关闭");
+
+    private final Integer status;
+    private final String name;
+}

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

@@ -1,51 +0,0 @@
-package cn.iocoder.yudao.framework.pay.core.enums;
-
-// TODO @芋艿:感觉情况有点多,得讨论下
-/**
- * 统一的渠道返回结果
- * @author  jason
- */
-public enum PayChannelRespEnum {
-
-    /**
-     * 接口通讯正常返回, 并明确处理成功 ,不需要通过查询或者回调接口 进行下一步处理
-     */
-    SYNC_SUCCESS,
-
-    /**
-     * 接口通讯正常返回, 但返回错误,并且不能通过重试解决的错误,
-     * 如提交失败, 业务错误(余额不足), 或者参数错误, 签名错误, 需要干预后才能处理
-     */
-    CAN_NOT_RETRY_FAILURE,
-
-
-    /**
-     * 接口通讯正常返回,
-     * 可以通过重试解决的错误. 如系统超时, 系统繁忙。状态未知 不能改变请求参数,如退款单请求号,重发请求
-     */
-    RETRY_FAILURE,
-
-
-    /**
-     * 接口通讯正常返回,但是处理结果 需要渠道回调进行下一步处理
-     */
-    PROCESSING_NOTIFY,
-
-
-    /**
-     * 接口通讯正常返回, 但是处理结果,需要调用查询接口 进行查询
-     */
-    PROCESSING_QUERY,
-
-
-    /**
-     * 本系统调用渠道接口异常, 渠道接口请求未正常发送, 本系统不可预知的异常,较少发生, 可认为失败。  不用重试.
-     */
-    CALL_EXCEPTION,
-
-
-    /**
-     * 本系统调用渠道接口成功, 但是未接受到请求结果,较少发生(需合理设置read time out )  结果未知。 需要调用查询接口进行查询
-     */
-    READ_TIME_OUT_EXCEPTION;
-}

+ 4 - 2
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayRefundController.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.userserver.modules.pay.controller.order;
 
+import cn.hutool.core.util.StrUtil;
 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.util.PaySeqUtils;
@@ -36,8 +37,9 @@ public class PayRefundController {
         PayRefundReqDTO req = PayRefundConvert.INSTANCE.convert(reqVO);
         req.setUserIp(getClientIP());
         //TODO 测试暂时模拟生成商户退款订单
-        req.setMerchantRefundNo(PaySeqUtils.genMerchantRefundNo());
-        //req.setMerchantRefundNo("MO202111210814084370000");
+        if(StrUtil.isEmpty(reqVO.getMerchantRefundNo())) {
+            req.setMerchantRefundNo(PaySeqUtils.genMerchantRefundNo());
+        }
         return CommonResult.success( PayRefundConvert.INSTANCE.convert(payRefundCoreService.submitRefundOrder(req)));
     }
 

+ 17 - 0
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundRespVO.java

@@ -15,6 +15,23 @@ import lombok.experimental.Accessors;
 @AllArgsConstructor
 public class PayRefundRespVO {
 
+    /**
+     * 渠道返回结果
+     * 退款处理中和退款成功  返回  1
+     * 失败和其他情况 返回 2
+     */
+    private Integer channelReturnResult;
+
+    /**
+     * 渠道返回code
+     */
+    private String channelReturnCode;
+
+    /**
+     * 渠道返回消息
+     */
+    private String  channelReturnMsg;
+
     /**
      * 支付退款单编号, 自增
      */

+ 1 - 1
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/order/PayRefundConvert.java

@@ -19,5 +19,5 @@ public interface PayRefundConvert {
 
     PayRefundReqDTO convert(PayRefundReqVO reqVO);
 
-    PayRefundRespVO convert(PayRefundRespDTO respBO);
+    PayRefundRespVO convert(PayRefundRespDTO req);
 }