Commit 5bf3045544ede01a9ced03a6040d7831cdc00b54

Authored by jason
1 parent 9a5f0853

优化退款逻辑

Showing 16 changed files with 62 additions and 156 deletions
sql/change_db.sql
@@ -3,3 +3,6 @@ CHANGE COLUMN `channel_notify_data` `channel_notify_data` VARCHAR(2048) CHARACTE @@ -3,3 +3,6 @@ CHANGE COLUMN `channel_notify_data` `channel_notify_data` VARCHAR(2048) CHARACTE
3 3
4 ALTER TABLE `ruoyi-vue-pro`.`pay_refund` 4 ALTER TABLE `ruoyi-vue-pro`.`pay_refund`
5 CHANGE COLUMN `req_no` `req_no` VARCHAR(64) NULL COMMENT '退款单请求号' ; 5 CHANGE COLUMN `req_no` `req_no` VARCHAR(64) NULL COMMENT '退款单请求号' ;
  6 +
  7 +ALTER TABLE `ruoyi-vue-pro`.`pay_refund`
  8 +DROP COLUMN `req_no`;
sql/ruoyi-vue-pro.sql
@@ -607,7 +607,7 @@ CREATE TABLE `pay_order_extension` ( @@ -607,7 +607,7 @@ CREATE TABLE `pay_order_extension` (
607 `user_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户 IP', 607 `user_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户 IP',
608 `status` tinyint NOT NULL COMMENT '支付状态', 608 `status` tinyint NOT NULL COMMENT '支付状态',
609 `channel_extras` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '支付渠道的额外参数', 609 `channel_extras` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '支付渠道的额外参数',
610 - `channel_notify_data` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '支付渠道异步通知的内容', 610 + `channel_notify_data` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '支付渠道异步通知的内容',
611 `creator` varchar(64) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '创建者', 611 `creator` varchar(64) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '创建者',
612 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 612 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
613 `updater` varchar(64) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '更新者', 613 `updater` varchar(64) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '更新者',
@@ -720,7 +720,6 @@ COMMIT; @@ -720,7 +720,6 @@ COMMIT;
720 DROP TABLE IF EXISTS `pay_refund`; 720 DROP TABLE IF EXISTS `pay_refund`;
721 CREATE TABLE `pay_refund` ( 721 CREATE TABLE `pay_refund` (
722 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '支付退款编号', 722 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '支付退款编号',
723 - `req_no` varchar(64) NOT NULL COMMENT '退款单请求号',  
724 `merchant_id` bigint NOT NULL COMMENT '商户编号', 723 `merchant_id` bigint NOT NULL COMMENT '商户编号',
725 `app_id` bigint NOT NULL COMMENT '应用编号', 724 `app_id` bigint NOT NULL COMMENT '应用编号',
726 `channel_id` bigint NOT NULL COMMENT '渠道编号', 725 `channel_id` bigint NOT NULL COMMENT '渠道编号',
yudao-admin-server/src/test/java/cn/iocoder/yudao/adminserver/modules/pay/service/refund/PayRefundServiceTest.java
@@ -43,7 +43,6 @@ public class PayRefundServiceTest extends BaseDbUnitTest { @@ -43,7 +43,6 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
43 public void testGetRefundPage() { 43 public void testGetRefundPage() {
44 // mock 数据 44 // mock 数据
45 PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到 45 PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到
46 - o.setReqNo("RF0000001");  
47 o.setMerchantId(1L); 46 o.setMerchantId(1L);
48 o.setAppId(1L); 47 o.setAppId(1L);
49 o.setChannelId(1L); 48 o.setChannelId(1L);
@@ -115,7 +114,6 @@ public class PayRefundServiceTest extends BaseDbUnitTest { @@ -115,7 +114,6 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
115 public void testGetRefundList() { 114 public void testGetRefundList() {
116 // mock 数据 115 // mock 数据
117 PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到 116 PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到
118 - o.setReqNo("RF0000001");  
119 o.setMerchantId(1L); 117 o.setMerchantId(1L);
120 o.setAppId(1L); 118 o.setAppId(1L);
121 o.setChannelId(1L); 119 o.setChannelId(1L);
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayRefundDO.java
@@ -36,20 +36,6 @@ public class PayRefundDO extends BaseDO { @@ -36,20 +36,6 @@ public class PayRefundDO extends BaseDO {
36 @TableId 36 @TableId
37 private Long id; 37 private Long id;
38 38
39 - /**  
40 - * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no  
41 - * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no  
42 - * 退款请求号。  
43 - * 标识一次退款请求,需要保证在交易号下唯一,如需部分退款,则此参数必传。  
44 - * 注:针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更,  
45 - * 防止该笔交易重复退款。支付宝会保证同样的退款请求号多次请求只会退一次。  
46 - * 退款单请求号,根据规则生成  
47 - * 例如说,R202109181134287570000  
48 - * 废弃,使用 merchantRefundNo 做退款请求号  
49 - */  
50 - @Deprecated  
51 - private String reqNo;  
52 -  
53 /** 39 /**
54 * 商户编号 40 * 商户编号
55 * 41 *
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/dto/PayRefundReqDTO.java
@@ -6,6 +6,10 @@ import lombok.Data; @@ -6,6 +6,10 @@ import lombok.Data;
6 import lombok.NoArgsConstructor; 6 import lombok.NoArgsConstructor;
7 import lombok.experimental.Accessors; 7 import lombok.experimental.Accessors;
8 8
  9 +import javax.validation.constraints.DecimalMin;
  10 +import javax.validation.constraints.NotEmpty;
  11 +import javax.validation.constraints.NotNull;
  12 +
9 /** 13 /**
10 * 退款申请单 Request DTO 14 * 退款申请单 Request DTO
11 */ 15 */
@@ -16,15 +20,17 @@ import lombok.experimental.Accessors; @@ -16,15 +20,17 @@ import lombok.experimental.Accessors;
16 @AllArgsConstructor 20 @AllArgsConstructor
17 public class PayRefundReqDTO { 21 public class PayRefundReqDTO {
18 22
19 - // TODO @jason:增加下 validation 注解哈  
20 /** 23 /**
21 * 支付订单编号 24 * 支付订单编号
22 */ 25 */
  26 + @NotNull(message = "支付订单编号不能为空")
23 private Long payOrderId; 27 private Long payOrderId;
24 28
25 /** 29 /**
26 * 退款金额 30 * 退款金额
27 */ 31 */
  32 + @NotNull(message = "退款金额不能为空")
  33 + @DecimalMin(value = "0", inclusive = false, message = "退款金额必须大于零")
28 private Long amount; 34 private Long amount;
29 35
30 /** 36 /**
@@ -35,8 +41,8 @@ public class PayRefundReqDTO { @@ -35,8 +41,8 @@ public class PayRefundReqDTO {
35 /** 41 /**
36 * 商户退款订单号 42 * 商户退款订单号
37 */ 43 */
38 - // TODO @jason:merchantRefundNo=》merchantRefundId,保持和 PayOrder 的 merchantOrderId 一致哈  
39 - private String merchantRefundNo; 44 + @NotEmpty(message = "商户退款订单号不能为空")
  45 + private String merchantRefundId;
40 46
41 /** 47 /**
42 * 用户 IP 48 * 用户 IP
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/dto/PayRefundRespDTO.java
@@ -17,24 +17,6 @@ import lombok.experimental.Accessors; @@ -17,24 +17,6 @@ import lombok.experimental.Accessors;
17 public class PayRefundRespDTO { 17 public class PayRefundRespDTO {
18 18
19 /** 19 /**
20 - * 渠道返回结果  
21 - * 退款处理中和退款成功 返回 1  
22 - * 失败和其他情况 返回 2  
23 - */  
24 - // TODO @jason:这个 result,可以使用 CommonResult 里呢  
25 - private Integer channelReturnResult;  
26 -  
27 - /**  
28 - * 渠道返回 code  
29 - */  
30 - private String channelReturnCode;  
31 -  
32 - /**  
33 - * 渠道返回消息  
34 - */  
35 - private String channelReturnMsg;  
36 -  
37 - /**  
38 * 支付退款单编号,自增 20 * 支付退款单编号,自增
39 */ 21 */
40 private Long refundId; 22 private Long refundId;
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundCoreServiceImpl.java
@@ -23,11 +23,11 @@ import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundReqDT @@ -23,11 +23,11 @@ import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundReqDT
23 import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundRespDTO; 23 import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundRespDTO;
24 import cn.iocoder.yudao.framework.pay.core.client.PayClient; 24 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
25 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; 25 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
  26 +import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
26 import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO; 27 import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
27 import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundNotifyDTO; 28 import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundNotifyDTO;
28 import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO; 29 import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO;
29 import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO; 30 import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
30 -import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum;  
31 import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum; 31 import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
32 import lombok.extern.slf4j.Slf4j; 32 import lombok.extern.slf4j.Slf4j;
33 import org.springframework.stereotype.Service; 33 import org.springframework.stereotype.Service;
@@ -88,11 +88,8 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService { @@ -88,11 +88,8 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
88 if (Objects.equals(req.getAmount(), order.getAmount())) { 88 if (Objects.equals(req.getAmount(), order.getAmount())) {
89 refundType = PayRefundTypeEnum.ALL; 89 refundType = PayRefundTypeEnum.ALL;
90 } 90 }
91 -  
92 PayOrderExtensionDO orderExtensionDO = payOrderExtensionCoreMapper.selectById(order.getSuccessExtensionId()); 91 PayOrderExtensionDO orderExtensionDO = payOrderExtensionCoreMapper.selectById(order.getSuccessExtensionId());
93 - PayRefundDO payRefundDO = payRefundCoreMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(), req.getMerchantRefundNo());  
94 - // 构造渠道的统一的退款请求参数  
95 - PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO(); 92 + PayRefundDO payRefundDO = payRefundCoreMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(), req.getMerchantRefundId());
96 if(Objects.nonNull(payRefundDO)){ 93 if(Objects.nonNull(payRefundDO)){
97 // 退款订单已经提交过。 94 // 退款订单已经提交过。
98 //TODO 校验相同退款单的金额 95 //TODO 校验相同退款单的金额
@@ -101,19 +98,10 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService { @@ -101,19 +98,10 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
101 || Objects.equals(PayRefundStatusEnum.CLOSE.getStatus(), payRefundDO.getStatus())) { 98 || Objects.equals(PayRefundStatusEnum.CLOSE.getStatus(), payRefundDO.getStatus())) {
102 //已成功退款 99 //已成功退款
103 throw exception(PAY_REFUND_SUCCEED); 100 throw exception(PAY_REFUND_SUCCEED);
104 - } else{  
105 - // TODO @jason:这里不用 else,简洁一些  
106 - // 保证商户退款单不变,重复向渠道发起退款。渠道保持幂等  
107 - unifiedReqDTO.setUserIp(req.getUserIp())  
108 - .setAmount(payRefundDO.getRefundAmount())  
109 - .setChannelOrderNo(payRefundDO.getChannelOrderNo())  
110 - .setPayTradeNo(payRefundDO.getTradeNo())  
111 - .setRefundReqNo(payRefundDO.getMerchantRefundNo())  
112 - .setReason(payRefundDO.getReason());  
113 } 101 }
114 - }else{  
115 - // 新生成退款单。 退款单入库 退款单状态:生成  
116 - // TODO @jason:封装一个小方法。插入退款单 102 + //可以重复提交,保证 退款请求号 一致,由渠道保证幂等
  103 + }else {
  104 + //成功,插入退款单 状态为生成.没有和渠道交互
117 payRefundDO = PayRefundDO.builder().channelOrderNo(order.getChannelOrderNo()) 105 payRefundDO = PayRefundDO.builder().channelOrderNo(order.getChannelOrderNo())
118 .appId(order.getAppId()) 106 .appId(order.getAppId())
119 .channelOrderNo(order.getChannelOrderNo()) 107 .channelOrderNo(order.getChannelOrderNo())
@@ -121,7 +109,7 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService { @@ -121,7 +109,7 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
121 .channelId(order.getChannelId()) 109 .channelId(order.getChannelId())
122 .merchantId(order.getMerchantId()) 110 .merchantId(order.getMerchantId())
123 .orderId(order.getId()) 111 .orderId(order.getId())
124 - .merchantRefundNo(req.getMerchantRefundNo()) 112 + .merchantRefundNo(req.getMerchantRefundId())
125 .notifyUrl(app.getRefundNotifyUrl()) 113 .notifyUrl(app.getRefundNotifyUrl())
126 .payAmount(order.getAmount()) 114 .payAmount(order.getAmount())
127 .refundAmount(req.getAmount()) 115 .refundAmount(req.getAmount())
@@ -134,38 +122,21 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService { @@ -134,38 +122,21 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
134 .type(refundType.getStatus()) 122 .type(refundType.getStatus())
135 .build(); 123 .build();
136 payRefundCoreMapper.insert(payRefundDO); 124 payRefundCoreMapper.insert(payRefundDO);
137 - // TODO @jason:这块的逻辑,和已存在的这块,貌似是统一的?  
138 - unifiedReqDTO.setUserIp(req.getUserIp())  
139 - .setAmount(payRefundDO.getRefundAmount())  
140 - .setChannelOrderNo(payRefundDO.getChannelOrderNo())  
141 - .setPayTradeNo(payRefundDO.getTradeNo())  
142 - .setRefundReqNo(payRefundDO.getMerchantRefundNo())  
143 - .setReason(req.getReason());  
144 } 125 }
  126 + PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO();
  127 + unifiedReqDTO.setUserIp(req.getUserIp())
  128 + .setAmount(req.getAmount())
  129 + .setChannelOrderNo(order.getChannelOrderNo())
  130 + .setPayTradeNo(orderExtensionDO.getNo())
  131 + .setMerchantRefundId(req.getMerchantRefundId())
  132 + .setReason(req.getReason());
145 // 向渠道发起退款申请 133 // 向渠道发起退款申请
146 - PayRefundUnifiedRespDTO refundUnifiedRespDTO = client.unifiedRefund(unifiedReqDTO);  
147 - // 构造退款申请返回对象  
148 - PayRefundRespDTO respDTO = new PayRefundRespDTO();  
149 - if (refundUnifiedRespDTO.getChannelResp() == PayChannelRefundRespEnum.SUCCESS  
150 - ||refundUnifiedRespDTO.getChannelResp() == PayChannelRefundRespEnum.PROCESSING) {  
151 - // 成功处理,在退款通知中处理, 这里不处理  
152 - respDTO.setChannelReturnResult(PayChannelRefundRespEnum.SUCCESS.getStatus());  
153 - respDTO.setRefundId(payRefundDO.getId());  
154 - }else {  
155 - // 失败返回错误给前端,可以重新发起退款,保证退款请求号(这里是商户退款单号), 避免重复退款。  
156 - // TODO @jason:失败的话,是不是可以跑出 ServiceException 业务异常。这样就是成功返回 refundId,失败业务异常  
157 - respDTO.setChannelReturnResult(PayChannelRefundRespEnum.FAILURE.getStatus());  
158 - // 更新退款单状态  
159 - PayRefundDO updatePayRefund = new PayRefundDO();  
160 - updatePayRefund.setId(payRefundDO.getId())  
161 - .setChannelErrorMsg(refundUnifiedRespDTO.getChannelMsg())  
162 - .setChannelErrorCode(refundUnifiedRespDTO.getChannelCode())  
163 - .setStatus(PayRefundStatusEnum.FAILURE.getStatus());  
164 - payRefundCoreMapper.updateById(updatePayRefund);  
165 - }  
166 - respDTO.setChannelReturnCode(refundUnifiedRespDTO.getChannelCode())  
167 - .setChannelReturnMsg(refundUnifiedRespDTO.getChannelMsg());  
168 - return respDTO; 134 + PayCommonResult<PayRefundUnifiedRespDTO> refundUnifiedResult = client.unifiedRefund(unifiedReqDTO);
  135 + //检查是否失败,失败抛出业务异常。
  136 + //TODO 渠道的异常记录
  137 + refundUnifiedResult.checkError();
  138 + //成功在 退款回调中处理
  139 + return PayRefundRespDTO.builder().refundId(payRefundDO.getId()).build();
169 } 140 }
170 141
171 142
@@ -187,7 +158,7 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService { @@ -187,7 +158,7 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
187 payRefundSuccess(refundNotify); 158 payRefundSuccess(refundNotify);
188 } else { 159 } else {
189 //TODO 支付异常, 支付宝似乎没有支付异常的通知。 160 //TODO 支付异常, 支付宝似乎没有支付异常的通知。
190 - // TODO @jason:那这里可以考虑打个 error logger 161 + // TODO @jason:那这里可以考虑打个 error logger @芋艿 微信是否存在支付异常通知
191 } 162 }
192 } 163 }
193 164
@@ -199,22 +170,22 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService { @@ -199,22 +170,22 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
199 throw exception(PAY_REFUND_NOT_FOUND); 170 throw exception(PAY_REFUND_NOT_FOUND);
200 } 171 }
201 172
202 - // 计算订单的状态。如果全部退款,则订单处于关闭。TODO @jason:建议这里按照金额来判断,因为可能退款多次  
203 - Integer type = refundDO.getType(); 173 + // 得到已退金额
  174 + PayOrderDO payOrderDO = payOrderCoreMapper.selectById(refundDO.getOrderId());
  175 + Long refundedAmount = payOrderDO.getRefundAmount();
  176 +
204 PayOrderStatusEnum orderStatus = PayOrderStatusEnum.SUCCESS; 177 PayOrderStatusEnum orderStatus = PayOrderStatusEnum.SUCCESS;
205 - if (PayRefundTypeEnum.ALL.getStatus().equals(type)){ 178 + if(Objects.equals(payOrderDO.getAmount(), refundedAmount+ refundDO.getRefundAmount())){
  179 + //支付金额 = 已退金额 + 本次退款金额。
206 orderStatus = PayOrderStatusEnum.CLOSED; 180 orderStatus = PayOrderStatusEnum.CLOSED;
207 } 181 }
208 - // 需更新已退金额  
209 - PayOrderDO payOrderDO = payOrderCoreMapper.selectById(refundDO.getOrderId());  
210 - Long refundedAmount = payOrderDO.getRefundAmount();  
211 // 更新支付订单 182 // 更新支付订单
212 PayOrderDO updateOrderDO = new PayOrderDO(); 183 PayOrderDO updateOrderDO = new PayOrderDO();
213 updateOrderDO.setId(refundDO.getOrderId()) 184 updateOrderDO.setId(refundDO.getOrderId())
214 .setRefundAmount(refundedAmount + refundDO.getRefundAmount()) 185 .setRefundAmount(refundedAmount + refundDO.getRefundAmount())
215 .setStatus(orderStatus.getStatus()) 186 .setStatus(orderStatus.getStatus())
216 .setRefundTimes(payOrderDO.getRefundTimes() + 1) 187 .setRefundTimes(payOrderDO.getRefundTimes() + 1)
217 - .setRefundStatus(type); 188 + .setRefundStatus(refundDO.getType());
218 payOrderCoreMapper.updateById(updateOrderDO); 189 payOrderCoreMapper.updateById(updateOrderDO);
219 190
220 // 更新退款订单 191 // 更新退款订单
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java
@@ -39,7 +39,7 @@ public interface PayClient { @@ -39,7 +39,7 @@ public interface PayClient {
39 * @param reqDTO 统一退款请求信息 39 * @param reqDTO 统一退款请求信息
40 * @return 各支付渠道的统一返回结果 40 * @return 各支付渠道的统一返回结果
41 */ 41 */
42 - PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO); 42 + PayCommonResult<PayRefundUnifiedRespDTO> unifiedRefund(PayRefundUnifiedReqDTO reqDTO);
43 43
44 /** 44 /**
45 * 解析支付退款通知数据 45 * 解析支付退款通知数据
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedReqDTO.java
@@ -43,7 +43,6 @@ public class PayRefundUnifiedReqDTO { @@ -43,7 +43,6 @@ public class PayRefundUnifiedReqDTO {
43 */ 43 */
44 private String payTradeNo; 44 private String payTradeNo;
45 45
46 - // TODO @jason:这个字段,要不就使用 merchantRefundId,更直接  
47 /** 46 /**
48 * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no 47 * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
49 * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no 48 * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no
@@ -51,7 +50,7 @@ public class PayRefundUnifiedReqDTO { @@ -51,7 +50,7 @@ public class PayRefundUnifiedReqDTO {
51 * 使用 商户的退款单号。{PayRefundDO 字段 merchantRefundNo} 50 * 使用 商户的退款单号。{PayRefundDO 字段 merchantRefundNo}
52 */ 51 */
53 @NotEmpty(message = "退款请求单号") 52 @NotEmpty(message = "退款请求单号")
54 - private String refundReqNo; 53 + private String merchantRefundId;
55 54
56 /** 55 /**
57 * 退款原因 56 * 退款原因
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedRespDTO.java
@@ -18,22 +18,8 @@ import lombok.experimental.Accessors; @@ -18,22 +18,8 @@ import lombok.experimental.Accessors;
18 @Data 18 @Data
19 public class PayRefundUnifiedRespDTO { 19 public class PayRefundUnifiedRespDTO {
20 20
21 - // TODO @jason:可以合并下。退款处理中、成功,都是成功;其它就业务失败。这样,可以复用 PayCommonResult;这个 RespDTO 可以返回渠道的退款编号  
22 /** 21 /**
23 - * 渠道的退款结果 22 + * 渠道退款单编号
24 */ 23 */
25 - private PayChannelRefundRespEnum channelResp;  
26 -  
27 - // TODO @json:channelReturnCode 和 channelReturnMsg 放到 PayCommonResult 里噶  
28 - /**  
29 - * 渠道返回码  
30 - */  
31 - private String channelCode;  
32 -  
33 - /**  
34 - * 渠道返回信息  
35 - */  
36 - private String channelMsg;  
37 -  
38 - //TODO 退款资金渠 ??? 24 + private String channelRefundId;
39 } 25 }
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
@@ -103,19 +103,19 @@ public abstract class AbstractPayClient&lt;Config extends PayClientConfig&gt; implemen @@ -103,19 +103,19 @@ public abstract class AbstractPayClient&lt;Config extends PayClientConfig&gt; implemen
103 103
104 104
105 @Override 105 @Override
106 - public PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {  
107 - PayRefundUnifiedRespDTO resp; 106 + public PayCommonResult<PayRefundUnifiedRespDTO> unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
  107 + PayCommonResult<PayRefundUnifiedRespDTO> resp;
108 try { 108 try {
109 resp = doUnifiedRefund(reqDTO); 109 resp = doUnifiedRefund(reqDTO);
110 } catch (Throwable ex) { 110 } catch (Throwable ex) {
111 // 记录异常日志 111 // 记录异常日志
112 log.error("[unifiedRefund][request({}) 发起退款失败]", toJsonString(reqDTO), ex); 112 log.error("[unifiedRefund][request({}) 发起退款失败]", toJsonString(reqDTO), ex);
113 - throw exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR); 113 + resp = PayCommonResult.error(ex);
114 } 114 }
115 return resp; 115 return resp;
116 } 116 }
117 117
118 118
119 - protected abstract PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable; 119 + protected abstract PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
120 120
121 } 121 }
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClient.java
@@ -3,9 +3,9 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; @@ -3,9 +3,9 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
3 import cn.hutool.core.bean.BeanUtil; 3 import cn.hutool.core.bean.BeanUtil;
4 import cn.hutool.core.date.DateUtil; 4 import cn.hutool.core.date.DateUtil;
5 import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping; 5 import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
  6 +import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
6 import cn.iocoder.yudao.framework.pay.core.client.dto.*; 7 import cn.iocoder.yudao.framework.pay.core.client.dto.*;
7 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; 8 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
8 -import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum;  
9 import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum; 9 import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
10 import com.alipay.api.AlipayApiException; 10 import com.alipay.api.AlipayApiException;
11 import com.alipay.api.AlipayConfig; 11 import com.alipay.api.AlipayConfig;
@@ -100,39 +100,32 @@ public abstract class AbstractAlipayClient extends AbstractPayClient&lt;AlipayPayCl @@ -100,39 +100,32 @@ public abstract class AbstractAlipayClient extends AbstractPayClient&lt;AlipayPayCl
100 * @return 退款请求 Response 100 * @return 退款请求 Response
101 */ 101 */
102 @Override 102 @Override
103 - protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) { 103 + protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
104 AlipayTradeRefundModel model=new AlipayTradeRefundModel(); 104 AlipayTradeRefundModel model=new AlipayTradeRefundModel();
105 model.setTradeNo(reqDTO.getChannelOrderNo()); 105 model.setTradeNo(reqDTO.getChannelOrderNo());
106 model.setOutTradeNo(reqDTO.getPayTradeNo()); 106 model.setOutTradeNo(reqDTO.getPayTradeNo());
107 - model.setOutRequestNo(reqDTO.getRefundReqNo()); 107 + model.setOutRequestNo(reqDTO.getMerchantRefundId());
108 model.setRefundAmount(calculateAmount(reqDTO.getAmount()).toString()); 108 model.setRefundAmount(calculateAmount(reqDTO.getAmount()).toString());
109 model.setRefundReason(reqDTO.getReason()); 109 model.setRefundReason(reqDTO.getReason());
110 AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest(); 110 AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest();
111 refundRequest.setBizModel(model); 111 refundRequest.setBizModel(model);
112 - PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO();  
113 try { 112 try {
114 AlipayTradeRefundResponse response = client.execute(refundRequest); 113 AlipayTradeRefundResponse response = client.execute(refundRequest);
115 log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", toJsonString(response)); 114 log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", toJsonString(response));
116 if (response.isSuccess()) { 115 if (response.isSuccess()) {
117 - //退款成功,更新为PROCESSING_NOTIFY, 而不是 SYNC_SUCCESS 通过支付宝回调接口处理。退款导致触发的异步通知,  
118 //退款导致触发的异步通知是发送到支付接口中设置的notify_url 116 //退款导致触发的异步通知是发送到支付接口中设置的notify_url
119 - //TODO 沙箱环境 返回 的tradeNo(渠道退款单号) 和 订单的tradNo 是一个值,是不是理解不对?  
120 - respDTO.setChannelResp(PayChannelRefundRespEnum.SUCCESS)  
121 - .setChannelCode(response.getCode())  
122 - .setChannelMsg(response.getMsg()); 117 + //支付宝不返回退款单号,设置为空
  118 + PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO();
  119 + respDTO.setChannelRefundId("");
  120 + return PayCommonResult.build(response.getCode(), response.getMsg(), respDTO, codeMapping);
123 }else{ 121 }else{
124 - respDTO.setChannelResp(PayChannelRefundRespEnum.FAILURE)  
125 - .setChannelCode(response.getSubCode())  
126 - .setChannelMsg(response.getSubMsg()); 122 + //失败。需要抛出异常
  123 + return PayCommonResult.build(response.getCode(), response.getMsg(), null, codeMapping);
127 } 124 }
128 - return respDTO;  
129 } catch (AlipayApiException e) { 125 } catch (AlipayApiException e) {
130 //TODO 记录异常日志 126 //TODO 记录异常日志
131 log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), e); 127 log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), e);
132 - respDTO.setChannelResp(PayChannelRefundRespEnum.FAILURE)  
133 - .setChannelCode(e.getErrCode())  
134 - .setChannelMsg(e.getErrMsg());  
135 - return respDTO; 128 + return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
136 } 129 }
137 } 130 }
138 131
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java
@@ -149,7 +149,7 @@ public class WXPubPayClient extends AbstractPayClient&lt;WXPayClientConfig&gt; { @@ -149,7 +149,7 @@ public class WXPubPayClient extends AbstractPayClient&lt;WXPayClientConfig&gt; {
149 149
150 150
151 @Override 151 @Override
152 - protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable { 152 + protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
153 //TODO 需要实现 153 //TODO 需要实现
154 throw new UnsupportedOperationException(); 154 throw new UnsupportedOperationException();
155 } 155 }
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayRefundController.java
@@ -37,8 +37,8 @@ public class PayRefundController { @@ -37,8 +37,8 @@ public class PayRefundController {
37 PayRefundReqDTO req = PayRefundConvert.INSTANCE.convert(reqVO); 37 PayRefundReqDTO req = PayRefundConvert.INSTANCE.convert(reqVO);
38 req.setUserIp(getClientIP()); 38 req.setUserIp(getClientIP());
39 //TODO 测试暂时模拟生成商户退款订单 39 //TODO 测试暂时模拟生成商户退款订单
40 - if(StrUtil.isEmpty(reqVO.getMerchantRefundNo())) {  
41 - req.setMerchantRefundNo(PaySeqUtils.genMerchantRefundNo()); 40 + if(StrUtil.isEmpty(reqVO.getMerchantRefundId())) {
  41 + req.setMerchantRefundId(PaySeqUtils.genMerchantRefundNo());
42 } 42 }
43 return CommonResult.success( PayRefundConvert.INSTANCE.convert(payRefundCoreService.submitRefundOrder(req))); 43 return CommonResult.success( PayRefundConvert.INSTANCE.convert(payRefundCoreService.submitRefundOrder(req)));
44 } 44 }
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundReqVO.java
@@ -29,6 +29,6 @@ public class PayRefundReqVO { @@ -29,6 +29,6 @@ public class PayRefundReqVO {
29 @ApiModelProperty(value = "商户退款订单号", required = true, example = "MR202111180000000001") 29 @ApiModelProperty(value = "商户退款订单号", required = true, example = "MR202111180000000001")
30 //TODO 测试暂时模拟生成 30 //TODO 测试暂时模拟生成
31 //@NotEmpty(message = "商户退款订单号") 31 //@NotEmpty(message = "商户退款订单号")
32 - private String merchantRefundNo; 32 + private String merchantRefundId;
33 33
34 } 34 }
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundRespVO.java
@@ -16,23 +16,6 @@ import lombok.experimental.Accessors; @@ -16,23 +16,6 @@ import lombok.experimental.Accessors;
16 public class PayRefundRespVO { 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 - /**  
36 * 支付退款单编号, 自增 19 * 支付退款单编号, 自增
37 */ 20 */
38 private Long refundId; 21 private Long refundId;