Commit 5bf3045544ede01a9ced03a6040d7831cdc00b54
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<Config extends PayClientConfig> implemen | @@ -103,19 +103,19 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> 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<AlipayPayCl | @@ -100,39 +100,32 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<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<WXPayClientConfig> { | @@ -149,7 +149,7 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> { | ||
| 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; |