Commit 08103685f1ab1f45af10e6ca5745975b4f8382b7

Authored by jason
1 parent 054c237b

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

Showing 25 changed files with 232 additions and 718 deletions
sql/change_db.sql 0 → 100644
  1 +ALTER TABLE `ruoyi-vue-pro`.`pay_order_extension`
  2 +CHANGE COLUMN `channel_notify_data` `channel_notify_data` VARCHAR(2048) CHARACTER SET 'utf8mb4' NULL DEFAULT NULL COMMENT '支付渠道异步通知的内容' ;
  3 +
  4 +ALTER TABLE `ruoyi-vue-pro`.`pay_refund`
  5 +CHANGE COLUMN `req_no` `req_no` VARCHAR(64) NULL COMMENT '退款单请求号' ;
... ...
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;
2 2  
3 3 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
4 4 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
5   -import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
6   -import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
7 5 import org.mapstruct.Mapper;
8   -import org.mapstruct.factory.Mappers;
9 6 import org.mapstruct.Mapping;
10 7 import org.mapstruct.Mappings;
  8 +import org.mapstruct.factory.Mappers;
11 9  
12 10 @Mapper
13 11 public interface PayRefundCoreConvert {
14 12  
15 13 PayRefundCoreConvert INSTANCE = Mappers.getMapper(PayRefundCoreConvert.class);
16 14  
17   - PayRefundPostReqDTO convert(PayRefundUnifiedRespDTO respDTO);
  15 +
18 16  
19 17 //TODO 太多需要处理了, 暂时不用
20 18 @Mappings(value = {
... ...
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 {
42 42 * 注:针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更,
43 43 * 防止该笔交易重复退款。支付宝会保证同样的退款请求号多次请求只会退一次。
44 44 * 退款单请求号,根据规则生成
45   - *
46 45 * 例如说,R202109181134287570000
  46 + * 废弃,使用 merchantRefundNo 做退款请求号
47 47 */
  48 + @Deprecated
48 49 private String reqNo;
49 50  
50 51 /**
... ... @@ -82,7 +83,7 @@ public class PayRefundDO extends BaseDO {
82 83 /**
83 84 * 交易订单号,根据规则生成
84 85 * 调用支付渠道时,使用该字段作为对接的订单号。
85   - * 1. 调用微信支付 https://api.mch.weixin.qq.com/pay/unifiedorder 时,使用该字段作为 out_trade_no
  86 + * 1. 调用微信支付 https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 时,使用该字段作为 out_trade_no
86 87 * 2. 调用支付宝 https://opendocs.alipay.com/apis 时,使用该字段作为 out_trade_no
87 88 * 这里对应 pay_extension 里面的 no
88 89 * 例如说,P202110132239124200055
... ... @@ -95,10 +96,20 @@ public class PayRefundDO extends BaseDO {
95 96 * 商户订单编号
96 97 */
97 98 private String merchantOrderId;
  99 +
98 100 /**
99   - * 商户退款订单号, 由商户系统产生, 由他们保证唯一,不能为空,通知商户时会传该字段。发送channel 使用 reqNo
  101 + * 商户退款订单号, 由商户系统产生, 由他们保证唯一,不能为空,通知商户时会传该字段。
100 102 * 例如说,内部系统 A 的退款订单号。需要保证每个 PayMerchantDO 唯一
101   - * TODO 芋艿:我理解 一个商户退款订单,可以对应多条退款记录, 因为有可能失败。但是 退款请求号 reqNo 必须唯一
  103 + * 个商户退款订单,对应一条退款请求记录。可多次提交。 渠道保持幂等
  104 + * 使用商户退款单,作为退款请求号
  105 + * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml 中的 out_refund_no
  106 + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
  107 + * 退款请求号。
  108 + * 标识一次退款请求,需要保证在交易号下唯一,如需部分退款,则此参数必传。
  109 + * 注:针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更,
  110 + * 防止该笔交易重复退款。支付宝会保证同样的退款请求号多次请求只会退一次。
  111 + * 退款单请求号,根据规则生成
  112 + * 例如说,R202109181134287570000
102 113 *
103 114 */
104 115 private String merchantRefundNo;
... ... @@ -151,6 +162,9 @@ public class PayRefundDO extends BaseDO {
151 162 */
152 163 private String channelOrderNo;
153 164 /**
  165 + * 微信中的 refund_id
  166 + * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml
  167 + * 支付宝没有
154 168 * 渠道退款单号,渠道返回
155 169 */
156 170 private String channelRefundNo;
... ...
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> {
15 15 default PayRefundDO selectByReqNo(String reqNo) {
16 16 return selectOne("req_no", reqNo);
17 17 }
  18 +
  19 + default PayRefundDO selectByTradeNoAndMerchantRefundNo(String tradeNo, String merchantRefundNo){
  20 + return selectOne("trade_no", tradeNo, "merchant_refund_no", merchantRefundNo);
  21 + }
18 22 }
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java
... ... @@ -46,9 +46,10 @@ public interface PayErrorCodeCoreConstants {
46 46 ErrorCode PAY_REFUND_AMOUNT_EXCEED = new ErrorCode(1007006000, "退款金额超过订单可退款金额");
47 47 ErrorCode PAY_REFUND_ALL_REFUNDED = new ErrorCode(1007006001, "订单已经全额退款");
48 48 ErrorCode PAY_REFUND_CHN_ORDER_NO_IS_NULL = new ErrorCode(1007006002, "该订单的渠道订单为空");
49   - ErrorCode PAY_REFUND_POST_HANDLER_NOT_FOUND = new ErrorCode(1007006003, "未找到对应的退款后置处理类");
  49 + ErrorCode PAY_REFUND_SUCCEED = new ErrorCode(1007006003, "已经退款成功");
50 50 ErrorCode PAY_REFUND_NOT_FOUND = new ErrorCode(1007006004, "支付退款单不存在");
51 51  
  52 +
52 53 /**
53 54 * ========== 支付商户信息 1-007-004-000 ==========
54 55 */
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayRefundStatusEnum.java
... ... @@ -9,10 +9,6 @@ public enum PayRefundStatusEnum {
9 9 CREATE(0, "退款订单生成"),
10 10 SUCCESS(1, "退款成功"),
11 11 FAILURE(2, "退款失败"),
12   - PROCESSING_NOTIFY(3,"退款中, 渠道通知结果"),
13   - PROCESSING_QUERY(4,"退款中, 系统查询结果"),
14   - UNKNOWN_RETRY(5,"状态未知,需要重试"),
15   - UNKNOWN_QUERY(6,"状态未知,系统查询结果"),
16 12 CLOSE(99, "退款关闭");
17 13  
18 14 private final Integer status;
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundAbstractChannelPostHandler.java deleted
1   -package cn.iocoder.yudao.coreservice.modules.pay.service.order;
2   -
3   -import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
4   -import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
5   -import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
6   -import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundCoreMapper;
7   -
8   -/**
9   - * 支付退款订单渠道返回后 , 后置处理抽象类, 处理公用的逻辑
10   - * @author jason
11   - */
12   -public abstract class PayRefundAbstractChannelPostHandler implements PayRefundChannelPostHandler {
13   -
14   - private final PayOrderCoreMapper payOrderCoreMapper;
15   - private final PayRefundCoreMapper payRefundMapper;
16   -
17   - public PayRefundAbstractChannelPostHandler(PayOrderCoreMapper payOrderCoreMapper,
18   - PayRefundCoreMapper payRefundMapper){
19   - this.payOrderCoreMapper = payOrderCoreMapper;
20   - this.payRefundMapper = payRefundMapper;
21   - }
22   -
23   -
24   - /**
25   - * 更新退款单
26   - * @param refundDO 需要更新的退款单信息
27   - */
28   - protected void updatePayRefund(PayRefundDO refundDO){
29   - payRefundMapper.updateById(refundDO);
30   - }
31   -
32   -
33   - /**
34   - * 更新原始支付订单
35   - * @param payOrderDO 支付订单信息
36   - */
37   - protected void updatePayOrder(PayOrderDO payOrderDO){
38   - payOrderCoreMapper.updateById(payOrderDO);
39   - }
40   -}
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundChannelPostHandler.java deleted
1   -package cn.iocoder.yudao.coreservice.modules.pay.service.order;
2   -
3   -import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
4   -import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
5   -
6   -/**
7   - * 支付退款订单 ,渠道返回后 后置处理
8   - *
9   - * @author jason
10   - */
11   -public interface PayRefundChannelPostHandler {
12   -
13   - /**
14   - * 支持的渠道返回值
15   - *
16   - * @return 支持的渠道返回值数组
17   - */
18   - PayChannelRespEnum[] supportHandleResp();
19   -
20   - /**
21   - * 根据渠道返回,处理支付退款单
22   - *
23   - * @param req 退款后置处理请求
24   - */
25   - void handleRefundChannelResp(PayRefundPostReqDTO req);
26   -}
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/dto/PayRefundPostReqDTO.java deleted
1   -package cn.iocoder.yudao.coreservice.modules.pay.service.order.dto;
2   -
3   -import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum;
4   -import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
5   -import lombok.AllArgsConstructor;
6   -import lombok.Builder;
7   -import lombok.Data;
8   -import lombok.NoArgsConstructor;
9   -import lombok.experimental.Accessors;
10   -
11   -/**
12   - * 退款后置处理请求 Request DTO
13   - */
14   -@Data
15   -@Accessors(chain = true)
16   -@Builder
17   -@NoArgsConstructor
18   -@AllArgsConstructor
19   -public class PayRefundPostReqDTO {
20   -
21   -
22   - /**
23   - * 渠道的通用返回结果
24   - */
25   - private PayChannelRespEnum respEnum;
26   -
27   -
28   -
29   - private PayRefundTypeEnum refundTypeEnum;
30   -
31   - /**
32   - * 已退款的总金额
33   - */
34   - private Long refundedAmount;
35   -
36   - /**
37   - * 本次退款金额
38   - */
39   - private Long refundAmount;
40   -
41   - /**
42   - * 已退款次数
43   - */
44   - private Integer refundedTimes;
45   -
46   -
47   - /**
48   - * 订单编号
49   - */
50   - private Long orderId;
51   -
52   - /**
53   - * 退款单编号
54   - */
55   - private Long refundId;
56   -
57   -
58   - /**
59   - * 渠道退款单号
60   - */
61   - private String channelRefundNo;
62   -
63   -
64   - /**
65   - * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_trade_no
66   - * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no
67   - * 支付交易号 {PayOrderExtensionDO no字段} 和 渠道订单号 不能同时为空
68   - */
69   - private String payTradeNo;
70   -
71   -
72   - /**
73   - * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
74   - * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
75   - * 退款请求单号 同一退款请求单号多次请求只退一笔。
76   - */
77   - private String refundReqNo;
78   -
79   -
80   -
81   - /**
82   - * 调用异常错误信息
83   - */
84   - private String exceptionMsg;
85   -
86   -
87   - /**
88   - * 渠道的错误码
89   - */
90   - private String channelErrCode;
91   -
92   -
93   - /**
94   - * 渠道的错误描述
95   - */
96   - private String channelErrMsg;
97   -
98   -
99   -}
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/dto/PayRefundRespDTO.java
... ... @@ -17,6 +17,23 @@ import lombok.experimental.Accessors;
17 17 public class PayRefundRespDTO {
18 18  
19 19 /**
  20 + * 渠道返回结果
  21 + * 退款处理中和退款成功 返回 1
  22 + * 失败和其他情况 返回 2
  23 + */
  24 + private Integer channelReturnResult;
  25 +
  26 + /**
  27 + * 渠道返回code
  28 + */
  29 + private String channelReturnCode;
  30 +
  31 + /**
  32 + * 渠道返回消息
  33 + */
  34 + private String channelReturnMsg;
  35 +
  36 + /**
20 37 * 支付退款单编号, 自增
21 38 */
22 39 private Long refundId;
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundCoreServiceImpl.java
1 1 package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4   -import cn.iocoder.yudao.coreservice.modules.pay.convert.order.PayRefundCoreConvert;
5 4 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
6 5 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
7 6 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;
14 13 import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum;
15 14 import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService;
16 15 import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
17   -import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
18 16 import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundReqDTO;
19 17 import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundNotifyDTO;
20 18 import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum;
... ... @@ -23,21 +21,18 @@ import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
23 21 import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayAppCoreService;
24 22 import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayChannelCoreService;
25 23 import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundCoreService;
26   -import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundChannelPostHandler;
27 24 import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundRespDTO;
28   -import cn.iocoder.yudao.coreservice.modules.pay.util.PaySeqUtils;
29 25 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
30 26 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
31 27 import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
32 28 import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO;
33 29 import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
  30 +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum;
34 31 import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
35   -import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
36 32 import lombok.extern.slf4j.Slf4j;
37 33 import org.springframework.stereotype.Service;
38 34 import org.springframework.transaction.annotation.Transactional;
39 35  
40   -import javax.annotation.PostConstruct;
41 36 import javax.annotation.Resource;
42 37 import java.util.*;
43 38  
... ... @@ -65,25 +60,6 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
65 60 @Resource
66 61 private PayClientFactory payClientFactory;
67 62  
68   - /**
69   - * 处理渠道返回结果的后置处理器 集合
70   - */
71   - @Resource
72   - private List<PayRefundChannelPostHandler> handlerList;
73   -
74   - private final EnumMap<PayChannelRespEnum, PayRefundChannelPostHandler> mapHandlers = new EnumMap<>(PayChannelRespEnum.class);
75   -
76   - @PostConstruct
77   - public void init(){
78   - if (Objects.nonNull(handlerList)) {
79   - handlerList.forEach(handler -> {
80   - for (PayChannelRespEnum item : handler.supportHandleResp()) {
81   - mapHandlers.put(item, handler);
82   - }
83   - });
84   - }
85   - }
86   -
87 63 @Override
88 64 @Transactional(rollbackFor = Exception.class)
89 65 public PayRefundRespDTO submitRefundOrder(PayRefundReqDTO req) {
... ... @@ -106,66 +82,89 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
106 82  
107 83 // 校验退款的条件
108 84 validatePayRefund(req, order);
109   -
110 85 // 退款类型
111 86 PayRefundTypeEnum refundType = PayRefundTypeEnum.SOME;
112 87 if (Objects.equals(req.getAmount(), order.getAmount())) {
113 88 refundType = PayRefundTypeEnum.ALL;
114 89 }
115   - // 退款单入库 退款单状态:生成, 没有和渠道产生交互
  90 +
116 91 PayOrderExtensionDO orderExtensionDO = payOrderExtensionCoreMapper.selectById(order.getSuccessExtensionId());
117   - PayRefundDO refundDO = PayRefundDO.builder().channelOrderNo(order.getChannelOrderNo())
118   - .appId(order.getAppId())
119   - .channelOrderNo(order.getChannelOrderNo())
120   - .channelCode(order.getChannelCode())
121   - .channelId(order.getChannelId())
122   - .merchantId(order.getMerchantId())
123   - .orderId(order.getId())
124   - .merchantRefundNo(req.getMerchantRefundNo())
125   - .notifyUrl(app.getRefundNotifyUrl())
126   - .payAmount(order.getAmount())
127   - .refundAmount(req.getAmount())
128   - .userIp(req.getUserIp())
129   - .merchantOrderId(order.getMerchantOrderId())
130   - .tradeNo(orderExtensionDO.getNo())
131   - .status(PayRefundStatusEnum.CREATE.getStatus())
132   - .reason(req.getReason())
133   - .notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus())
134   - .reqNo(PaySeqUtils.genRefundReqNo())
135   - .type(refundType.getStatus())
136   - .build();
137   - payRefundCoreMapper.insert(refundDO);
138   -
139   - // 调用渠道进行退款
140   - PayRefundUnifiedReqDTO unifiedReqDTO = PayRefundUnifiedReqDTO.builder()
141   - .userIp(req.getUserIp())
142   - .channelOrderNo(refundDO.getChannelOrderNo())
143   - .payTradeNo(refundDO.getTradeNo())
144   - .refundReqNo(refundDO.getReqNo())
145   - .amount(req.getAmount())
146   - .reason(refundDO.getReason())
147   - .build();
148   - PayRefundUnifiedRespDTO refundUnifiedRespDTO = client.unifiedRefund(unifiedReqDTO);
149   - // 根据渠道返回,获取退款后置处理,由postHandler 进行处理
150   - PayRefundChannelPostHandler payRefundChannelPostHandler = mapHandlers.get(refundUnifiedRespDTO.getRespEnum());
151   - if (Objects.isNull(payRefundChannelPostHandler)) {
152   - throw exception(PAY_REFUND_POST_HANDLER_NOT_FOUND);
153   - }
154   - PayRefundPostReqDTO dto = PayRefundCoreConvert.INSTANCE.convert(refundUnifiedRespDTO);
155   - dto.setRefundAmount(req.getAmount())
156   - .setRefundedAmount(order.getRefundAmount())
157   - .setRefundedTimes(order.getRefundTimes())
158   - .setRefundId(refundDO.getId())
159   - .setOrderId(order.getId())
160   - .setRefundTypeEnum(refundType);
161   - //调用退款的后置处理
162   - payRefundChannelPostHandler.handleRefundChannelResp(dto);
163   -
164   - return PayRefundRespDTO.builder().refundId(refundDO.getId()).build();
  92 + PayRefundDO payRefundDO = payRefundCoreMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(), req.getMerchantRefundNo());
  93 + //构造渠道的统一的退款请求参数
  94 + PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO();
  95 + if(Objects.nonNull(payRefundDO)){
  96 + //退款订单已经提交过。
  97 + //TODO 校验相同退款单的金额
  98 + if(Objects.equals(PayRefundStatusEnum.SUCCESS.getStatus(), payRefundDO.getStatus())
  99 + || Objects.equals(PayRefundStatusEnum.CLOSE.getStatus(), payRefundDO.getStatus())){
  100 + //已成功退款
  101 + throw exception(PAY_REFUND_SUCCEED);
  102 + }else{
  103 + //保证商户退款单不变,重复向渠道发起退款。渠道保持幂等
  104 + unifiedReqDTO.setUserIp(req.getUserIp())
  105 + .setAmount(payRefundDO.getRefundAmount())
  106 + .setChannelOrderNo(payRefundDO.getChannelOrderNo())
  107 + .setPayTradeNo(payRefundDO.getTradeNo())
  108 + .setRefundReqNo(payRefundDO.getMerchantRefundNo())
  109 + .setReason(payRefundDO.getReason());
  110 + }
  111 + }else{
  112 + //新生成退款单。 退款单入库 退款单状态:生成
  113 + payRefundDO = PayRefundDO.builder().channelOrderNo(order.getChannelOrderNo())
  114 + .appId(order.getAppId())
  115 + .channelOrderNo(order.getChannelOrderNo())
  116 + .channelCode(order.getChannelCode())
  117 + .channelId(order.getChannelId())
  118 + .merchantId(order.getMerchantId())
  119 + .orderId(order.getId())
  120 + .merchantRefundNo(req.getMerchantRefundNo())
  121 + .notifyUrl(app.getRefundNotifyUrl())
  122 + .payAmount(order.getAmount())
  123 + .refundAmount(req.getAmount())
  124 + .userIp(req.getUserIp())
  125 + .merchantOrderId(order.getMerchantOrderId())
  126 + .tradeNo(orderExtensionDO.getNo())
  127 + .status(PayRefundStatusEnum.CREATE.getStatus())
  128 + .reason(req.getReason())
  129 + .notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus())
  130 + .type(refundType.getStatus())
  131 + .build();
  132 + payRefundCoreMapper.insert(payRefundDO);
  133 + unifiedReqDTO.setUserIp(req.getUserIp())
  134 + .setAmount(payRefundDO.getRefundAmount())
  135 + .setChannelOrderNo(payRefundDO.getChannelOrderNo())
  136 + .setPayTradeNo(payRefundDO.getTradeNo())
  137 + .setRefundReqNo(payRefundDO.getMerchantRefundNo())
  138 + .setReason(req.getReason());
  139 + }
  140 + //向渠道发起退款申请
  141 + PayRefundUnifiedRespDTO refundUnifiedRespDTO = client.unifiedRefund(unifiedReqDTO);
  142 + //构造退款申请返回对象
  143 + PayRefundRespDTO respDTO = new PayRefundRespDTO();
  144 + if(refundUnifiedRespDTO.getChannelResp() == PayChannelRefundRespEnum.SUCCESS
  145 + ||refundUnifiedRespDTO.getChannelResp() == PayChannelRefundRespEnum.PROCESSING ){
  146 + //成功处理, 在退款通知中处理, 这里不处理
  147 + respDTO.setChannelReturnResult(PayChannelRefundRespEnum.SUCCESS.getStatus());
  148 + respDTO.setRefundId(payRefundDO.getId());
  149 + }else {
  150 + //失败返回错误给前端,可以重新发起退款,保证退款请求号(这里是商户退款单号), 避免重复退款。
  151 + respDTO.setChannelReturnResult(PayChannelRefundRespEnum.FAILURE.getStatus());
  152 + //更新退款单状态
  153 + PayRefundDO updatePayRefund = new PayRefundDO();
  154 + updatePayRefund.setId(payRefundDO.getId())
  155 + .setChannelErrorMsg(refundUnifiedRespDTO.getChannelMsg())
  156 + .setChannelErrorCode(refundUnifiedRespDTO.getChannelCode())
  157 + .setStatus(PayRefundStatusEnum.FAILURE.getStatus());
  158 + payRefundCoreMapper.updateById(updatePayRefund);
  159 + }
  160 + respDTO.setChannelReturnCode(refundUnifiedRespDTO.getChannelCode())
  161 + .setChannelReturnMsg(refundUnifiedRespDTO.getChannelMsg());
  162 + return respDTO;
165 163 }
166 164  
167 165  
168 166 @Override
  167 + @Transactional(rollbackFor = Exception.class)
169 168 public void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) {
170 169 log.info("[notifyPayRefund][channelId({}) 回调数据({})]", channelId, notifyData.getBody());
171 170 // 校验支付渠道是否有效
... ... @@ -178,49 +177,52 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
178 177 }
179 178 // 解析渠道退款通知数据, 统一处理
180 179 PayRefundNotifyDTO refundNotify = client.parseRefundNotify(notifyData);
181   -
182   - // TODO @jason:抽一个 notifyPayRefundSuccess 方法
183 180 if (Objects.equals(PayNotifyRefundStatusEnum.SUCCESS,refundNotify.getStatus())){
184   - // 退款成功。 支付宝只有退款成功才会发通知
185   - PayRefundDO refundDO = payRefundCoreMapper.selectByReqNo(refundNotify.getReqNo());
186   - if (refundDO == null) {
187   - log.error("不存在 seqNo 为{} 的支付退款单",refundNotify.getReqNo());
188   - throw exception(PAY_REFUND_NOT_FOUND);
189   - }
190   - Long refundAmount = refundDO.getRefundAmount();
191   - Integer type = refundDO.getType();
192   - PayOrderStatusEnum orderStatus = PayOrderStatusEnum.SUCCESS;
193   - if(PayRefundTypeEnum.ALL.getStatus().equals(type)){
194   - orderStatus = PayOrderStatusEnum.CLOSED;
195   - }
196   - // 更新支付订单
197   - PayOrderDO payOrderDO = payOrderCoreMapper.selectById(refundDO.getOrderId());
198   - // 需更新已退金额
199   - Long refundedAmount = payOrderDO.getRefundAmount();
200   - PayOrderDO updateOrderDO = new PayOrderDO();
201   - updateOrderDO.setId(refundDO.getOrderId())
202   - .setRefundAmount(refundedAmount + refundAmount)
203   - .setStatus(orderStatus.getStatus())
204   - .setRefundStatus(type);
205   - payOrderCoreMapper.updateById(updateOrderDO);
206   -
207   - // 跟新退款订单
208   - PayRefundDO updateRefundDO = new PayRefundDO();
209   - updateRefundDO.setId(refundDO.getId())
210   - .setSuccessTime(refundNotify.getRefundSuccessTime())
211   - .setChannelRefundNo(refundNotify.getChannelOrderNo())
212   - .setTradeNo(refundNotify.getTradeNo())
213   - .setNotifyTime(new Date())
214   - .setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
215   - payRefundCoreMapper.updateById(updateRefundDO);
216   -
217   - //插入退款通知记录
218   - // TODO 通知商户成功或者失败. 现在通知似乎没有实现, 只是回调
219   - payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
220   - .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refundDO.getId()).build());
  181 + payRefundSuccess(refundNotify);
221 182 } else {
222   - //TODO 退款失败
  183 + //TODO 支付异常, 支付宝似乎没有支付异常的通知。
  184 + }
  185 + }
  186 +
  187 + private void payRefundSuccess(PayRefundNotifyDTO refundNotify) {
  188 + PayRefundDO refundDO = payRefundCoreMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(), refundNotify.getReqNo());
  189 + if (refundDO == null) {
  190 + log.error("不存在 seqNo 为{} 的支付退款单",refundNotify.getReqNo());
  191 + throw exception(PAY_REFUND_NOT_FOUND);
  192 + }
  193 + Long refundAmount = refundDO.getRefundAmount();
  194 + Integer type = refundDO.getType();
  195 + PayOrderStatusEnum orderStatus = PayOrderStatusEnum.SUCCESS;
  196 + if(PayRefundTypeEnum.ALL.getStatus().equals(type)){
  197 + orderStatus = PayOrderStatusEnum.CLOSED;
223 198 }
  199 + // 更新支付订单
  200 + PayOrderDO payOrderDO = payOrderCoreMapper.selectById(refundDO.getOrderId());
  201 + // 需更新已退金额
  202 + Long refundedAmount = payOrderDO.getRefundAmount();
  203 + PayOrderDO updateOrderDO = new PayOrderDO();
  204 + updateOrderDO.setId(refundDO.getOrderId())
  205 + .setRefundAmount(refundedAmount + refundAmount)
  206 + .setStatus(orderStatus.getStatus())
  207 + .setRefundTimes(payOrderDO.getRefundTimes()+1)
  208 + .setRefundStatus(type);
  209 +
  210 + payOrderCoreMapper.updateById(updateOrderDO);
  211 +
  212 + // 跟新退款订单
  213 + PayRefundDO updateRefundDO = new PayRefundDO();
  214 + updateRefundDO.setId(refundDO.getId())
  215 + .setSuccessTime(refundNotify.getRefundSuccessTime())
  216 + .setChannelRefundNo(refundNotify.getChannelOrderNo())
  217 + .setTradeNo(refundNotify.getTradeNo())
  218 + .setNotifyTime(new Date())
  219 + .setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
  220 + payRefundCoreMapper.updateById(updateRefundDO);
  221 +
  222 + //插入退款通知记录
  223 + // TODO 通知商户成功或者失败. 现在通知似乎没有实现, 只是回调
  224 + payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
  225 + .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refundDO.getId()).build());
224 226 }
225 227  
226 228 /**
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/handler/PayRefundChannelFailedHandler.java deleted
1   -package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl.handler;
2   -
3   -import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
4   -import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
5   -import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
6   -import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundCoreMapper;
7   -import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum;
8   -import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
9   -import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService;
10   -import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
11   -import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler;
12   -import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
13   -import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
14   -import org.springframework.stereotype.Service;
15   -
16   -import javax.annotation.Resource;
17   -import java.util.Optional;
18   -
19   -/**
20   - * 支付退款订单渠道返回失败的后置处理类
21   - * {@link PayChannelRespEnum#CALL_EXCEPTION}
22   - * {@link PayChannelRespEnum#CAN_NOT_RETRY_FAILURE}
23   - */
24   -@Service
25   -public class PayRefundChannelFailedHandler extends PayRefundAbstractChannelPostHandler {
26   -
27   - @Resource
28   - private PayNotifyCoreService payNotifyCoreService;
29   -
30   - public PayRefundChannelFailedHandler(PayOrderCoreMapper payOrderCoreMapper, PayRefundCoreMapper payRefundMapper) {
31   - super(payOrderCoreMapper, payRefundMapper);
32   - }
33   -
34   - @Override
35   - public PayChannelRespEnum[] supportHandleResp() {
36   - return new PayChannelRespEnum[] {PayChannelRespEnum.CALL_EXCEPTION, PayChannelRespEnum.CAN_NOT_RETRY_FAILURE};
37   - }
38   -
39   -
40   - @Override
41   - public void handleRefundChannelResp(PayRefundPostReqDTO req) {
42   - //退款失败
43   - //更新退款单表
44   - PayRefundDO updateRefundDO = new PayRefundDO();
45   -
46   - updateRefundDO.setId(req.getRefundId())
47   - .setStatus(PayRefundStatusEnum.FAILURE.getStatus())
48   - .setChannelErrorCode(req.getChannelErrCode())
49   - .setChannelErrorMsg(Optional.ofNullable(req.getChannelErrMsg())
50   - .orElse(req.getExceptionMsg()));
51   - updatePayRefund(updateRefundDO);
52   - PayOrderDO updateOrderDO = new PayOrderDO();
53   - //更新订单表
54   - updateOrderDO.setId(req.getOrderId())
55   - .setRefundTimes(req.getRefundedTimes() + 1);
56   - updatePayOrder(updateOrderDO);
57   - // 立刻插入退款通知记录
58   - // TODO 通知商户成功或者失败. 现在通知似乎没有实现, 只是回调
59   - payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
60   - .type(PayNotifyTypeEnum.REFUND.getType()).dataId(req.getRefundId()).build());
61   - }
62   -}
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/handler/PayRefundChannelNotifyHandler.java deleted
1   -package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl.handler;
2   -
3   -import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
4   -import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
5   -import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
6   -import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundCoreMapper;
7   -import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
8   -import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler;
9   -import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
10   -import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
11   -import org.springframework.stereotype.Service;
12   -
13   -/**
14   - * 支付退款订单渠道返回通知 {@link PayChannelRespEnum#PROCESSING_NOTIFY},后置处理类
15   - * 支付宝退款单好像没有回调, 微信会触发回调
16   - */
17   -@Service
18   -public class PayRefundChannelNotifyHandler extends PayRefundAbstractChannelPostHandler {
19   -
20   - public PayRefundChannelNotifyHandler(PayOrderCoreMapper payOrderCoreMapper,
21   - PayRefundCoreMapper payRefundMapper) {
22   - super(payOrderCoreMapper, payRefundMapper);
23   - }
24   -
25   - @Override
26   - public PayChannelRespEnum[] supportHandleResp() {
27   - return new PayChannelRespEnum[] {PayChannelRespEnum.PROCESSING_NOTIFY};
28   - }
29   -
30   - @Override
31   - public void handleRefundChannelResp(PayRefundPostReqDTO req) {
32   - PayRefundDO updateRefundDO = new PayRefundDO();
33   - //更新退款单表
34   - updateRefundDO.setId(req.getRefundId())
35   - .setStatus(PayRefundStatusEnum.PROCESSING_NOTIFY.getStatus());
36   - updatePayRefund(updateRefundDO);
37   -
38   - PayOrderDO updateOrderDO = new PayOrderDO();
39   - //更新订单表
40   - updateOrderDO.setId(req.getOrderId())
41   - .setRefundTimes(req.getRefundedTimes() + 1);
42   - updatePayOrder(updateOrderDO);
43   -
44   - }
45   -}
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/handler/PayRefundChannelQueryHandler.java deleted
1   -package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl.handler;
2   -
3   -import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
4   -import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
5   -import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
6   -import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundCoreMapper;
7   -import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
8   -import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler;
9   -import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
10   -import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
11   -import org.springframework.stereotype.Service;
12   -
13   -import java.util.Objects;
14   -
15   -/**
16   - * 支付退款订单渠道返回需调用查询接口的后置处理类
17   - * {@link PayChannelRespEnum#PROCESSING_QUERY} //TODO 芋道源码 是不是微信有这样的情况
18   - * {@link PayChannelRespEnum#READ_TIME_OUT_EXCEPTION}
19   - */
20   -@Service
21   -public class PayRefundChannelQueryHandler extends PayRefundAbstractChannelPostHandler {
22   -
23   -
24   - public PayRefundChannelQueryHandler(PayOrderCoreMapper payOrderCoreMapper,
25   - PayRefundCoreMapper payRefundMapper) {
26   - super(payOrderCoreMapper, payRefundMapper);
27   - }
28   -
29   - @Override
30   - public PayChannelRespEnum[] supportHandleResp() {
31   - return new PayChannelRespEnum[]{PayChannelRespEnum.PROCESSING_QUERY, PayChannelRespEnum.READ_TIME_OUT_EXCEPTION};
32   - }
33   -
34   - @Override
35   - public void handleRefundChannelResp(PayRefundPostReqDTO req) {
36   - final PayChannelRespEnum respEnum = req.getRespEnum();
37   - PayRefundStatusEnum refundStatus =
38   - Objects.equals(PayChannelRespEnum.PROCESSING_QUERY, respEnum) ? PayRefundStatusEnum.PROCESSING_QUERY
39   - : PayRefundStatusEnum.UNKNOWN_QUERY;
40   - //更新退款单表
41   - PayRefundDO updateRefundDO = new PayRefundDO();
42   - updateRefundDO.setId(req.getRefundId())
43   - .setStatus(refundStatus.getStatus())
44   - .setChannelErrorCode(req.getChannelErrCode())
45   - .setChannelErrorMsg(req.getChannelErrMsg());
46   - updatePayRefund(updateRefundDO);
47   -
48   - PayOrderDO updateOrderDO = new PayOrderDO();
49   - //更新订单表
50   - updateOrderDO.setId(req.getOrderId())
51   - .setRefundTimes(req.getRefundedTimes() + 1);
52   - updatePayOrder(updateOrderDO);
53   -
54   - //TODO 发起查询任务
55   - }
56   -}
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/handler/PayRefundChannelRetryHandler.java deleted
1   -package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl.handler;
2   -
3   -import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
4   -import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
5   -import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
6   -import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundCoreMapper;
7   -import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
8   -import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler;
9   -import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
10   -import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
11   -import org.springframework.stereotype.Service;
12   -
13   -/**
14   - * 支付退款订单渠道返回重试的后置处理类
15   - * {@link PayChannelRespEnum#RETRY_FAILURE}
16   - */
17   -@Service
18   -public class PayRefundChannelRetryHandler extends PayRefundAbstractChannelPostHandler {
19   -
20   -
21   - public PayRefundChannelRetryHandler(PayOrderCoreMapper payOrderCoreMapper,
22   - PayRefundCoreMapper payRefundMapper) {
23   - super(payOrderCoreMapper, payRefundMapper);
24   - }
25   -
26   - @Override
27   - public PayChannelRespEnum[] supportHandleResp() {
28   - return new PayChannelRespEnum[] {PayChannelRespEnum.RETRY_FAILURE};
29   - }
30   -
31   - @Override
32   - public void handleRefundChannelResp(PayRefundPostReqDTO req) {
33   -
34   - PayRefundDO updateRefundDO = new PayRefundDO();
35   - //更新退款单表
36   - updateRefundDO.setId(req.getRefundId())
37   - .setStatus(PayRefundStatusEnum.UNKNOWN_RETRY.getStatus())
38   - .setChannelErrorCode(req.getChannelErrCode())
39   - .setChannelErrorMsg(req.getChannelErrMsg());
40   - updatePayRefund(updateRefundDO);
41   -
42   - PayOrderDO updateOrderDO = new PayOrderDO();
43   - //更新订单表
44   - updateOrderDO.setId(req.getOrderId())
45   - .setRefundTimes(req.getRefundedTimes() + 1);
46   - updatePayOrder(updateOrderDO);
47   -
48   - //TODO 发起重试任务
49   - }
50   -}
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/handler/PayRefundChannelSuccessHandler.java deleted
1   -package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl.handler;
2   -
3   -import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
4   -import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
5   -import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
6   -import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundCoreMapper;
7   -import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum;
8   -import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
9   -import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService;
10   -import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
11   -import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler;
12   -import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundPostReqDTO;
13   -import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
14   -import org.springframework.stereotype.Service;
15   -
16   -import javax.annotation.Resource;
17   -import java.util.Date;
18   -/**
19   - * 支付退款订单渠道返回退款成功的后置处理类
20   - * {@link PayChannelRespEnum#SYNC_SUCCESS}
21   - */
22   -@Service
23   -public class PayRefundChannelSuccessHandler extends PayRefundAbstractChannelPostHandler {
24   -
25   -
26   - @Resource
27   - private PayNotifyCoreService payNotifyCoreService;
28   -
29   -
30   - public PayRefundChannelSuccessHandler(PayOrderCoreMapper payOrderCoreMapper,
31   - PayRefundCoreMapper payRefundMapper) {
32   - super(payOrderCoreMapper, payRefundMapper);
33   - }
34   -
35   - @Override
36   - public PayChannelRespEnum[] supportHandleResp() {
37   - return new PayChannelRespEnum[]{PayChannelRespEnum.SYNC_SUCCESS};
38   - }
39   -
40   - @Override
41   - public void handleRefundChannelResp(PayRefundPostReqDTO req) {
42   - //退款成功
43   - PayRefundDO updateRefundDO = new PayRefundDO();
44   - //更新退款单表
45   - updateRefundDO.setId(req.getRefundId())
46   - .setStatus(PayRefundStatusEnum.SUCCESS.getStatus())
47   - .setChannelRefundNo(req.getChannelRefundNo())
48   - .setSuccessTime(new Date());
49   - updatePayRefund(updateRefundDO);
50   -
51   - PayOrderDO updateOrderDO = new PayOrderDO();
52   - //更新订单表
53   - updateOrderDO.setId(req.getOrderId())
54   - .setRefundTimes(req.getRefundedTimes() + 1)
55   - .setRefundStatus(req.getRefundTypeEnum().getStatus())
56   - .setRefundAmount(req.getRefundedAmount()+ req.getRefundAmount());
57   - updatePayOrder(updateOrderDO);
58   -
59   - // 立刻插入退款通知记录
60   - // TODO 通知商户成功或者失败. 现在通知似乎没有实现, 只是回调
61   - payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
62   - .type(PayNotifyTypeEnum.REFUND.getType()).dataId(req.getRefundId()).build());
63   - }
64   -}
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 {
48 48 * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
49 49 * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
50 50 * 退款请求单号 同一退款请求单号多次请求只退一笔。
  51 + * 使用 商户的退款单号。{PayRefundDO 字段 merchantRefundNo}
51 52 */
52 53 @NotEmpty(message = "退款请求单号")
53 54 private String refundReqNo;
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedRespDTO.java
1 1 package cn.iocoder.yudao.framework.pay.core.client.dto;
2 2  
3   -import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
  3 +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum;
4 4 import lombok.AllArgsConstructor;
5 5 import lombok.Builder;
6 6 import lombok.Data;
7 7 import lombok.NoArgsConstructor;
8 8 import lombok.experimental.Accessors;
9   -
10   -import javax.validation.constraints.NotEmpty;
11 9 /**
12   - * 统一 退款 Response DTO
  10 + * 统一退款 Response DTO
13 11 *
14 12 * @author jason
15 13 */
... ... @@ -19,55 +17,20 @@ import javax.validation.constraints.NotEmpty;
19 17 @AllArgsConstructor
20 18 @Data
21 19 public class PayRefundUnifiedRespDTO {
22   -
23   -
24   -
25   - /**
26   - * 渠道的通用返回结果
27   - */
28   - private PayChannelRespEnum respEnum;
29   -
30   -
31   -
32   - /**
33   - * 渠道退款单号
34   - */
35   - private String channelRefundNo;
36   -
37   -
38   - /**
39   - * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_trade_no
40   - * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no
41   - * 支付交易号 {PayOrderExtensionDO no字段} 和 渠道订单号 不能同时为空
42   - */
43   - private String payTradeNo;
44   -
45   -
46   - /**
47   - * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
48   - * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
49   - * 退款请求单号 同一退款请求单号多次请求只退一笔。
50   - */
51   - private String refundReqNo;
52   -
53   -
54   -
55 20 /**
56   - * 调用异常错误信息
  21 + * 渠道的退款结果
57 22 */
58   - private String exceptionMsg;
59   -
  23 + private PayChannelRefundRespEnum channelResp;
60 24  
61 25 /**
62   - * 渠道的错误码
  26 + * 渠道返回码
63 27 */
64   - private String channelErrCode;
65   -
  28 + private String channelCode;
66 29  
67 30 /**
68   - * 渠道的错误描述
  31 + * 渠道返回信息
69 32 */
70   - private String channelErrMsg;
  33 + private String channelMsg;
71 34  
72 35 //TODO 退款资金渠 ???
73 36 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
1 1 package cn.iocoder.yudao.framework.pay.core.client.impl;
2 2  
3 3 import cn.hutool.extra.validation.ValidationUtil;
  4 +import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
4 5 import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
5 6 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
6 7 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
... ... @@ -8,11 +9,9 @@ import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
8 9 import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
9 10 import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO;
10 11 import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
11   -import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
12 12 import lombok.extern.slf4j.Slf4j;
13 13  
14   -import java.net.SocketTimeoutException;
15   -
  14 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
16 15 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
17 16  
18 17 /**
... ... @@ -108,20 +107,10 @@ public abstract class AbstractPayClient&lt;Config extends PayClientConfig&gt; implemen
108 107 PayRefundUnifiedRespDTO resp;
109 108 try {
110 109 resp = doUnifiedRefund(reqDTO);
111   - } catch (SocketTimeoutException ex){
112   - // 网络 read time out 异常
113   - log.error("[unifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), ex);
114   - return PayRefundUnifiedRespDTO.builder()
115   - .exceptionMsg(ex.getMessage())
116   - .respEnum(PayChannelRespEnum.READ_TIME_OUT_EXCEPTION)
117   - .build();
118   - } catch (Throwable ex) {
119   - // 打印异常日志
  110 + } catch (Throwable ex) {
  111 + // 记录异常日志
120 112 log.error("[unifiedRefund][request({}) 发起退款失败]", toJsonString(reqDTO), ex);
121   - return PayRefundUnifiedRespDTO.builder()
122   - .exceptionMsg(ex.getMessage())
123   - .respEnum(PayChannelRespEnum.CALL_EXCEPTION)
124   - .build();
  113 + throw exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR);
125 114 }
126 115 return resp;
127 116 }
... ...
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;
5 5 import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
6 6 import cn.iocoder.yudao.framework.pay.core.client.dto.*;
7 7 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
8   -import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
  8 +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum;
9 9 import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
10 10 import com.alipay.api.AlipayApiException;
11 11 import com.alipay.api.AlipayConfig;
... ... @@ -17,7 +17,6 @@ import com.alipay.api.response.AlipayTradeRefundResponse;
17 17 import lombok.SneakyThrows;
18 18 import lombok.extern.slf4j.Slf4j;
19 19  
20   -import java.net.SocketTimeoutException;
21 20 import java.nio.charset.StandardCharsets;
22 21 import java.util.Map;
23 22  
... ... @@ -118,43 +117,21 @@ public abstract class AbstractAlipayClient extends AbstractPayClient&lt;AlipayPayCl
118 117 //退款成功,更新为PROCESSING_NOTIFY, 而不是 SYNC_SUCCESS 通过支付宝回调接口处理。退款导致触发的异步通知,
119 118 //退款导致触发的异步通知是发送到支付接口中设置的notify_url
120 119 //TODO 沙箱环境 返回 的tradeNo(渠道退款单号) 和 订单的tradNo 是一个值,是不是理解不对?
121   - respDTO.setRespEnum(PayChannelRespEnum.PROCESSING_NOTIFY);
  120 + respDTO.setChannelResp(PayChannelRefundRespEnum.SUCCESS)
  121 + .setChannelCode(response.getCode())
  122 + .setChannelMsg(response.getMsg());
122 123 }else{
123   - //特殊处理 sub_code ACQ.SYSTEM_ERROR(系统错误), 需要调用重试任务
124   - //沙箱环境返回的貌似是”aop.ACQ.SYSTEM_ERROR“, 用contain
125   - if (response.getSubCode().contains("ACQ.SYSTEM_ERROR")) {
126   - respDTO.setRespEnum(PayChannelRespEnum.RETRY_FAILURE)
127   - .setChannelErrMsg(response.getSubMsg())
128   - .setChannelErrCode(response.getSubCode());
129   - }else{
130   - //交易已关闭,需要查询确认退款是否已经完成
131   - if("ACQ.TRADE_HAS_CLOSE".equals(response.getSubCode())){
132   - respDTO.setRespEnum(PayChannelRespEnum.PROCESSING_QUERY)
133   - .setChannelErrMsg(response.getSubMsg())
134   - .setChannelErrCode(response.getSubCode());
135   - }else {
136   - //其他当做不可以重试的错误
137   - respDTO.setRespEnum(PayChannelRespEnum.CAN_NOT_RETRY_FAILURE)
138   - .setChannelErrCode(response.getSubCode())
139   - .setChannelErrMsg(response.getSubMsg());
140   - }
141   - }
  124 + respDTO.setChannelResp(PayChannelRefundRespEnum.FAILURE)
  125 + .setChannelCode(response.getSubCode())
  126 + .setChannelMsg(response.getSubMsg());
142 127 }
143 128 return respDTO;
144 129 } catch (AlipayApiException e) {
145 130 //TODO 记录异常日志
146 131 log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), e);
147   - Throwable cause = e.getCause();
148   - //网络 read time out 异常, 退款状态未知
149   - if (cause instanceof SocketTimeoutException) {
150   - respDTO.setExceptionMsg(e.getMessage())
151   - .setRespEnum(PayChannelRespEnum.READ_TIME_OUT_EXCEPTION);
152   - }else{
153   - respDTO.setExceptionMsg(e.getMessage())
154   - .setChannelErrCode(e.getErrCode())
155   - .setChannelErrMsg(e.getErrMsg())
156   - .setRespEnum(PayChannelRespEnum.CALL_EXCEPTION);
157   - }
  132 + respDTO.setChannelResp(PayChannelRefundRespEnum.FAILURE)
  133 + .setChannelCode(e.getErrCode())
  134 + .setChannelMsg(e.getErrMsg());
158 135 return respDTO;
159 136 }
160 137 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelRefundRespEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.pay.core.enums;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * 渠道统一的退款返回结果
  8 + *
  9 + * @author jason
  10 + */
  11 +@Getter
  12 +@AllArgsConstructor
  13 +public enum PayChannelRefundRespEnum {
  14 + SUCCESS(1, "退款成功"),
  15 + FAILURE(2, "退款失败"),
  16 + PROCESSING(3,"退款处理中"),
  17 + CLOSED(4, "退款关闭");
  18 +
  19 + private final Integer status;
  20 + private final String name;
  21 +}
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelRespEnum.java deleted
1   -package cn.iocoder.yudao.framework.pay.core.enums;
2   -
3   -// TODO @芋艿:感觉情况有点多,得讨论下
4   -/**
5   - * 统一的渠道返回结果
6   - * @author jason
7   - */
8   -public enum PayChannelRespEnum {
9   -
10   - /**
11   - * 接口通讯正常返回, 并明确处理成功 ,不需要通过查询或者回调接口 进行下一步处理
12   - */
13   - SYNC_SUCCESS,
14   -
15   - /**
16   - * 接口通讯正常返回, 但返回错误,并且不能通过重试解决的错误,
17   - * 如提交失败, 业务错误(余额不足), 或者参数错误, 签名错误, 需要干预后才能处理
18   - */
19   - CAN_NOT_RETRY_FAILURE,
20   -
21   -
22   - /**
23   - * 接口通讯正常返回,
24   - * 可以通过重试解决的错误. 如系统超时, 系统繁忙。状态未知 不能改变请求参数,如退款单请求号,重发请求
25   - */
26   - RETRY_FAILURE,
27   -
28   -
29   - /**
30   - * 接口通讯正常返回,但是处理结果 需要渠道回调进行下一步处理
31   - */
32   - PROCESSING_NOTIFY,
33   -
34   -
35   - /**
36   - * 接口通讯正常返回, 但是处理结果,需要调用查询接口 进行查询
37   - */
38   - PROCESSING_QUERY,
39   -
40   -
41   - /**
42   - * 本系统调用渠道接口异常, 渠道接口请求未正常发送, 本系统不可预知的异常,较少发生, 可认为失败。 不用重试.
43   - */
44   - CALL_EXCEPTION,
45   -
46   -
47   - /**
48   - * 本系统调用渠道接口成功, 但是未接受到请求结果,较少发生(需合理设置read time out ) 结果未知。 需要调用查询接口进行查询
49   - */
50   - READ_TIME_OUT_EXCEPTION;
51   -}
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayRefundController.java
1 1 package cn.iocoder.yudao.userserver.modules.pay.controller.order;
2 2  
  3 +import cn.hutool.core.util.StrUtil;
3 4 import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundCoreService;
4 5 import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundReqDTO;
5 6 import cn.iocoder.yudao.coreservice.modules.pay.util.PaySeqUtils;
... ... @@ -36,8 +37,9 @@ public class PayRefundController {
36 37 PayRefundReqDTO req = PayRefundConvert.INSTANCE.convert(reqVO);
37 38 req.setUserIp(getClientIP());
38 39 //TODO 测试暂时模拟生成商户退款订单
39   - req.setMerchantRefundNo(PaySeqUtils.genMerchantRefundNo());
40   - //req.setMerchantRefundNo("MO202111210814084370000");
  40 + if(StrUtil.isEmpty(reqVO.getMerchantRefundNo())) {
  41 + req.setMerchantRefundNo(PaySeqUtils.genMerchantRefundNo());
  42 + }
41 43 return CommonResult.success( PayRefundConvert.INSTANCE.convert(payRefundCoreService.submitRefundOrder(req)));
42 44 }
43 45  
... ...
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundRespVO.java
... ... @@ -16,6 +16,23 @@ import lombok.experimental.Accessors;
16 16 public class PayRefundRespVO {
17 17  
18 18 /**
  19 + * 渠道返回结果
  20 + * 退款处理中和退款成功 返回 1
  21 + * 失败和其他情况 返回 2
  22 + */
  23 + private Integer channelReturnResult;
  24 +
  25 + /**
  26 + * 渠道返回code
  27 + */
  28 + private String channelReturnCode;
  29 +
  30 + /**
  31 + * 渠道返回消息
  32 + */
  33 + private String channelReturnMsg;
  34 +
  35 + /**
19 36 * 支付退款单编号, 自增
20 37 */
21 38 private Long refundId;
... ...
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/order/PayRefundConvert.java
... ... @@ -19,5 +19,5 @@ public interface PayRefundConvert {
19 19  
20 20 PayRefundReqDTO convert(PayRefundReqVO reqVO);
21 21  
22   - PayRefundRespVO convert(PayRefundRespDTO respBO);
  22 + PayRefundRespVO convert(PayRefundRespDTO req);
23 23 }
... ...