mirror of
				https://github.com/YunaiV/ruoyi-vue-pro.git
				synced 2025-10-31 18:49:06 +08:00 
			
		
		
		
	优化退款逻辑
This commit is contained in:
		| @ -3,3 +3,6 @@ CHANGE COLUMN `channel_notify_data` `channel_notify_data` VARCHAR(2048) CHARACTE | |||||||
|  |  | ||||||
| ALTER TABLE `ruoyi-vue-pro`.`pay_refund` | ALTER TABLE `ruoyi-vue-pro`.`pay_refund` | ||||||
| CHANGE COLUMN `req_no` `req_no` VARCHAR(64) NULL COMMENT '退款单请求号' ; | CHANGE COLUMN `req_no` `req_no` VARCHAR(64) NULL COMMENT '退款单请求号' ; | ||||||
|  |  | ||||||
|  | ALTER TABLE `ruoyi-vue-pro`.`pay_refund` | ||||||
|  | DROP COLUMN `req_no`; | ||||||
|  | |||||||
| @ -607,7 +607,7 @@ CREATE TABLE `pay_order_extension` ( | |||||||
|   `user_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户 IP', |   `user_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户 IP', | ||||||
|   `status` tinyint NOT NULL COMMENT '支付状态', |   `status` tinyint NOT NULL COMMENT '支付状态', | ||||||
|   `channel_extras` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '支付渠道的额外参数', |   `channel_extras` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '支付渠道的额外参数', | ||||||
|   `channel_notify_data` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '支付渠道异步通知的内容', |   `channel_notify_data` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '支付渠道异步通知的内容', | ||||||
|   `creator` varchar(64) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '创建者', |   `creator` varchar(64) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '创建者', | ||||||
|   `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', |   `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', | ||||||
|   `updater` varchar(64) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '更新者', |   `updater` varchar(64) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '更新者', | ||||||
| @ -720,7 +720,6 @@ COMMIT; | |||||||
| DROP TABLE IF EXISTS `pay_refund`; | DROP TABLE IF EXISTS `pay_refund`; | ||||||
| CREATE TABLE `pay_refund` ( | CREATE TABLE `pay_refund` ( | ||||||
|   `id` bigint NOT NULL AUTO_INCREMENT COMMENT '支付退款编号', |   `id` bigint NOT NULL AUTO_INCREMENT COMMENT '支付退款编号', | ||||||
|   `req_no` varchar(64) NOT NULL COMMENT '退款单请求号', |  | ||||||
|   `merchant_id` bigint NOT NULL COMMENT '商户编号', |   `merchant_id` bigint NOT NULL COMMENT '商户编号', | ||||||
|   `app_id` bigint NOT NULL COMMENT '应用编号', |   `app_id` bigint NOT NULL COMMENT '应用编号', | ||||||
|   `channel_id` bigint NOT NULL COMMENT '渠道编号', |   `channel_id` bigint NOT NULL COMMENT '渠道编号', | ||||||
|  | |||||||
| @ -43,7 +43,6 @@ public class PayRefundServiceTest extends BaseDbUnitTest { | |||||||
|     public void testGetRefundPage() { |     public void testGetRefundPage() { | ||||||
|         // mock 数据 |         // mock 数据 | ||||||
|         PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到 |         PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到 | ||||||
|             o.setReqNo("RF0000001"); |  | ||||||
|             o.setMerchantId(1L); |             o.setMerchantId(1L); | ||||||
|             o.setAppId(1L); |             o.setAppId(1L); | ||||||
|             o.setChannelId(1L); |             o.setChannelId(1L); | ||||||
| @ -115,7 +114,6 @@ public class PayRefundServiceTest extends BaseDbUnitTest { | |||||||
|     public void testGetRefundList() { |     public void testGetRefundList() { | ||||||
|         // mock 数据 |         // mock 数据 | ||||||
|         PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到 |         PayRefundDO dbRefund = randomPojo(PayRefundDO.class, o -> { // 等会查询到 | ||||||
|             o.setReqNo("RF0000001"); |  | ||||||
|             o.setMerchantId(1L); |             o.setMerchantId(1L); | ||||||
|             o.setAppId(1L); |             o.setAppId(1L); | ||||||
|             o.setChannelId(1L); |             o.setChannelId(1L); | ||||||
|  | |||||||
| @ -36,20 +36,6 @@ public class PayRefundDO extends BaseDO { | |||||||
|     @TableId |     @TableId | ||||||
|     private Long id; |     private Long id; | ||||||
|  |  | ||||||
|      /** |  | ||||||
|      * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no |  | ||||||
|      * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no |  | ||||||
|      * 退款请求号。 |  | ||||||
|      * 标识一次退款请求,需要保证在交易号下唯一,如需部分退款,则此参数必传。 |  | ||||||
|      * 注:针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更, |  | ||||||
|      * 防止该笔交易重复退款。支付宝会保证同样的退款请求号多次请求只会退一次。 |  | ||||||
|      * 退款单请求号,根据规则生成 |  | ||||||
|      * 例如说,R202109181134287570000 |  | ||||||
|       * 废弃,使用 merchantRefundNo 做退款请求号 |  | ||||||
|      */ |  | ||||||
|     @Deprecated |  | ||||||
|     private String reqNo; |  | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 商户编号 |      * 商户编号 | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -6,6 +6,10 @@ import lombok.Data; | |||||||
| import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||||
| import lombok.experimental.Accessors; | import lombok.experimental.Accessors; | ||||||
|  |  | ||||||
|  | import javax.validation.constraints.DecimalMin; | ||||||
|  | import javax.validation.constraints.NotEmpty; | ||||||
|  | import javax.validation.constraints.NotNull; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 退款申请单 Request DTO |  * 退款申请单 Request DTO | ||||||
|  */ |  */ | ||||||
| @ -16,15 +20,17 @@ import lombok.experimental.Accessors; | |||||||
| @AllArgsConstructor | @AllArgsConstructor | ||||||
| public class PayRefundReqDTO { | public class PayRefundReqDTO { | ||||||
|  |  | ||||||
|     // TODO @jason:增加下 validation 注解哈 |  | ||||||
|     /** |     /** | ||||||
|      * 支付订单编号 |      * 支付订单编号 | ||||||
|      */ |      */ | ||||||
|  |     @NotNull(message = "支付订单编号不能为空") | ||||||
|     private Long payOrderId; |     private Long payOrderId; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 退款金额 |      * 退款金额 | ||||||
|      */ |      */ | ||||||
|  |     @NotNull(message = "退款金额不能为空") | ||||||
|  |     @DecimalMin(value = "0", inclusive = false, message = "退款金额必须大于零") | ||||||
|     private Long amount; |     private Long amount; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @ -35,8 +41,8 @@ public class PayRefundReqDTO { | |||||||
|     /** |     /** | ||||||
|      * 商户退款订单号 |      * 商户退款订单号 | ||||||
|      */ |      */ | ||||||
|     // TODO @jason:merchantRefundNo=》merchantRefundId,保持和 PayOrder 的 merchantOrderId 一致哈 |     @NotEmpty(message = "商户退款订单号不能为空") | ||||||
|     private String merchantRefundNo; |     private String merchantRefundId; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 用户 IP |      * 用户 IP | ||||||
|  | |||||||
| @ -16,24 +16,6 @@ import lombok.experimental.Accessors; | |||||||
| @AllArgsConstructor | @AllArgsConstructor | ||||||
| public class PayRefundRespDTO { | public class PayRefundRespDTO { | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 渠道返回结果 |  | ||||||
|      * 退款处理中和退款成功  返回  1 |  | ||||||
|      * 失败和其他情况 返回 2 |  | ||||||
|      */ |  | ||||||
|     // TODO @jason:这个 result,可以使用 CommonResult 里呢 |  | ||||||
|     private Integer channelReturnResult; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 渠道返回 code |  | ||||||
|      */ |  | ||||||
|     private String channelReturnCode; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 渠道返回消息 |  | ||||||
|      */ |  | ||||||
|     private String  channelReturnMsg; |  | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 支付退款单编号,自增 |      * 支付退款单编号,自增 | ||||||
|      */ |      */ | ||||||
|  | |||||||
| @ -23,11 +23,11 @@ import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundReqDT | |||||||
| import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundRespDTO; | import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundRespDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClient; | import cn.iocoder.yudao.framework.pay.core.client.PayClient; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; | import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundNotifyDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundNotifyDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO; | import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum; |  | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum; | import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum; | ||||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||||
| @ -88,11 +88,8 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService { | |||||||
|         if (Objects.equals(req.getAmount(), order.getAmount())) { |         if (Objects.equals(req.getAmount(), order.getAmount())) { | ||||||
|             refundType = PayRefundTypeEnum.ALL; |             refundType = PayRefundTypeEnum.ALL; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         PayOrderExtensionDO orderExtensionDO = payOrderExtensionCoreMapper.selectById(order.getSuccessExtensionId()); |         PayOrderExtensionDO orderExtensionDO = payOrderExtensionCoreMapper.selectById(order.getSuccessExtensionId()); | ||||||
|         PayRefundDO payRefundDO = payRefundCoreMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(), req.getMerchantRefundNo()); |         PayRefundDO payRefundDO = payRefundCoreMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(), req.getMerchantRefundId()); | ||||||
|         // 构造渠道的统一的退款请求参数 |  | ||||||
|         PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO(); |  | ||||||
|         if(Objects.nonNull(payRefundDO)){ |         if(Objects.nonNull(payRefundDO)){ | ||||||
|             // 退款订单已经提交过。 |             // 退款订单已经提交过。 | ||||||
|             //TODO 校验相同退款单的金额 |             //TODO 校验相同退款单的金额 | ||||||
| @ -101,19 +98,10 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService { | |||||||
|                 || Objects.equals(PayRefundStatusEnum.CLOSE.getStatus(), payRefundDO.getStatus())) { |                 || Objects.equals(PayRefundStatusEnum.CLOSE.getStatus(), payRefundDO.getStatus())) { | ||||||
|                 //已成功退款 |                 //已成功退款 | ||||||
|                throw exception(PAY_REFUND_SUCCEED); |                throw exception(PAY_REFUND_SUCCEED); | ||||||
|             } else{ |  | ||||||
|                 // TODO @jason:这里不用 else,简洁一些 |  | ||||||
|                 // 保证商户退款单不变,重复向渠道发起退款。渠道保持幂等 |  | ||||||
|                 unifiedReqDTO.setUserIp(req.getUserIp()) |  | ||||||
|                              .setAmount(payRefundDO.getRefundAmount()) |  | ||||||
|                              .setChannelOrderNo(payRefundDO.getChannelOrderNo()) |  | ||||||
|                              .setPayTradeNo(payRefundDO.getTradeNo()) |  | ||||||
|                              .setRefundReqNo(payRefundDO.getMerchantRefundNo()) |  | ||||||
|                              .setReason(payRefundDO.getReason()); |  | ||||||
|             } |             } | ||||||
|         }else{ |             //可以重复提交,保证 退款请求号 一致,由渠道保证幂等 | ||||||
|             // 新生成退款单。 退款单入库 退款单状态:生成 |         }else { | ||||||
|             // TODO @jason:封装一个小方法。插入退款单 |             //成功,插入退款单 状态为生成.没有和渠道交互 | ||||||
|             payRefundDO = PayRefundDO.builder().channelOrderNo(order.getChannelOrderNo()) |             payRefundDO = PayRefundDO.builder().channelOrderNo(order.getChannelOrderNo()) | ||||||
|                     .appId(order.getAppId()) |                     .appId(order.getAppId()) | ||||||
|                     .channelOrderNo(order.getChannelOrderNo()) |                     .channelOrderNo(order.getChannelOrderNo()) | ||||||
| @ -121,7 +109,7 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService { | |||||||
|                     .channelId(order.getChannelId()) |                     .channelId(order.getChannelId()) | ||||||
|                     .merchantId(order.getMerchantId()) |                     .merchantId(order.getMerchantId()) | ||||||
|                     .orderId(order.getId()) |                     .orderId(order.getId()) | ||||||
|                     .merchantRefundNo(req.getMerchantRefundNo()) |                     .merchantRefundNo(req.getMerchantRefundId()) | ||||||
|                     .notifyUrl(app.getRefundNotifyUrl()) |                     .notifyUrl(app.getRefundNotifyUrl()) | ||||||
|                     .payAmount(order.getAmount()) |                     .payAmount(order.getAmount()) | ||||||
|                     .refundAmount(req.getAmount()) |                     .refundAmount(req.getAmount()) | ||||||
| @ -134,38 +122,21 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService { | |||||||
|                     .type(refundType.getStatus()) |                     .type(refundType.getStatus()) | ||||||
|                     .build(); |                     .build(); | ||||||
|             payRefundCoreMapper.insert(payRefundDO); |             payRefundCoreMapper.insert(payRefundDO); | ||||||
|             // TODO @jason:这块的逻辑,和已存在的这块,貌似是统一的? |         } | ||||||
|  |         PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO(); | ||||||
|         unifiedReqDTO.setUserIp(req.getUserIp()) |         unifiedReqDTO.setUserIp(req.getUserIp()) | ||||||
|                     .setAmount(payRefundDO.getRefundAmount()) |                 .setAmount(req.getAmount()) | ||||||
|                     .setChannelOrderNo(payRefundDO.getChannelOrderNo()) |                 .setChannelOrderNo(order.getChannelOrderNo()) | ||||||
|                     .setPayTradeNo(payRefundDO.getTradeNo()) |                 .setPayTradeNo(orderExtensionDO.getNo()) | ||||||
|                     .setRefundReqNo(payRefundDO.getMerchantRefundNo()) |                 .setMerchantRefundId(req.getMerchantRefundId()) | ||||||
|                 .setReason(req.getReason()); |                 .setReason(req.getReason()); | ||||||
|         } |  | ||||||
|         // 向渠道发起退款申请 |         // 向渠道发起退款申请 | ||||||
|         PayRefundUnifiedRespDTO refundUnifiedRespDTO = client.unifiedRefund(unifiedReqDTO); |         PayCommonResult<PayRefundUnifiedRespDTO> refundUnifiedResult = client.unifiedRefund(unifiedReqDTO); | ||||||
|         // 构造退款申请返回对象 |         //检查是否失败,失败抛出业务异常。 | ||||||
|         PayRefundRespDTO respDTO = new PayRefundRespDTO(); |         //TODO 渠道的异常记录 | ||||||
|         if (refundUnifiedRespDTO.getChannelResp() == PayChannelRefundRespEnum.SUCCESS |         refundUnifiedResult.checkError(); | ||||||
|             ||refundUnifiedRespDTO.getChannelResp() == PayChannelRefundRespEnum.PROCESSING) { |         //成功在 退款回调中处理 | ||||||
|             // 成功处理,在退款通知中处理, 这里不处理 |         return PayRefundRespDTO.builder().refundId(payRefundDO.getId()).build(); | ||||||
|             respDTO.setChannelReturnResult(PayChannelRefundRespEnum.SUCCESS.getStatus()); |  | ||||||
|             respDTO.setRefundId(payRefundDO.getId()); |  | ||||||
|         }else { |  | ||||||
|             // 失败返回错误给前端,可以重新发起退款,保证退款请求号(这里是商户退款单号), 避免重复退款。 |  | ||||||
|             // TODO @jason:失败的话,是不是可以跑出 ServiceException 业务异常。这样就是成功返回 refundId,失败业务异常 |  | ||||||
|             respDTO.setChannelReturnResult(PayChannelRefundRespEnum.FAILURE.getStatus()); |  | ||||||
|             // 更新退款单状态 |  | ||||||
|             PayRefundDO updatePayRefund = new PayRefundDO(); |  | ||||||
|             updatePayRefund.setId(payRefundDO.getId()) |  | ||||||
|                     .setChannelErrorMsg(refundUnifiedRespDTO.getChannelMsg()) |  | ||||||
|                     .setChannelErrorCode(refundUnifiedRespDTO.getChannelCode()) |  | ||||||
|                     .setStatus(PayRefundStatusEnum.FAILURE.getStatus()); |  | ||||||
|             payRefundCoreMapper.updateById(updatePayRefund); |  | ||||||
|         } |  | ||||||
|         respDTO.setChannelReturnCode(refundUnifiedRespDTO.getChannelCode()) |  | ||||||
|                 .setChannelReturnMsg(refundUnifiedRespDTO.getChannelMsg()); |  | ||||||
|         return respDTO; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -187,7 +158,7 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService { | |||||||
|             payRefundSuccess(refundNotify); |             payRefundSuccess(refundNotify); | ||||||
|         } else { |         } else { | ||||||
|             //TODO 支付异常, 支付宝似乎没有支付异常的通知。 |             //TODO 支付异常, 支付宝似乎没有支付异常的通知。 | ||||||
|             // TODO @jason:那这里可以考虑打个 error logger |             // TODO @jason:那这里可以考虑打个 error logger @芋艿 微信是否存在支付异常通知 | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @ -199,22 +170,22 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService { | |||||||
|             throw exception(PAY_REFUND_NOT_FOUND); |             throw exception(PAY_REFUND_NOT_FOUND); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // 计算订单的状态。如果全部退款,则订单处于关闭。TODO @jason:建议这里按照金额来判断,因为可能退款多次 |         // 得到已退金额 | ||||||
|         Integer type = refundDO.getType(); |  | ||||||
|         PayOrderStatusEnum orderStatus = PayOrderStatusEnum.SUCCESS; |  | ||||||
|         if (PayRefundTypeEnum.ALL.getStatus().equals(type)){ |  | ||||||
|             orderStatus = PayOrderStatusEnum.CLOSED; |  | ||||||
|         } |  | ||||||
|         // 需更新已退金额 |  | ||||||
|         PayOrderDO payOrderDO = payOrderCoreMapper.selectById(refundDO.getOrderId()); |         PayOrderDO payOrderDO = payOrderCoreMapper.selectById(refundDO.getOrderId()); | ||||||
|         Long refundedAmount = payOrderDO.getRefundAmount(); |         Long refundedAmount = payOrderDO.getRefundAmount(); | ||||||
|  |  | ||||||
|  |         PayOrderStatusEnum orderStatus = PayOrderStatusEnum.SUCCESS; | ||||||
|  |         if(Objects.equals(payOrderDO.getAmount(), refundedAmount+ refundDO.getRefundAmount())){ | ||||||
|  |             //支付金额  = 已退金额 + 本次退款金额。 | ||||||
|  |             orderStatus = PayOrderStatusEnum.CLOSED; | ||||||
|  |         } | ||||||
|         // 更新支付订单 |         // 更新支付订单 | ||||||
|         PayOrderDO updateOrderDO = new PayOrderDO(); |         PayOrderDO updateOrderDO = new PayOrderDO(); | ||||||
|         updateOrderDO.setId(refundDO.getOrderId()) |         updateOrderDO.setId(refundDO.getOrderId()) | ||||||
|                 .setRefundAmount(refundedAmount + refundDO.getRefundAmount()) |                 .setRefundAmount(refundedAmount + refundDO.getRefundAmount()) | ||||||
|                 .setStatus(orderStatus.getStatus()) |                 .setStatus(orderStatus.getStatus()) | ||||||
|                 .setRefundTimes(payOrderDO.getRefundTimes() + 1) |                 .setRefundTimes(payOrderDO.getRefundTimes() + 1) | ||||||
|                 .setRefundStatus(type); |                 .setRefundStatus(refundDO.getType()); | ||||||
|         payOrderCoreMapper.updateById(updateOrderDO); |         payOrderCoreMapper.updateById(updateOrderDO); | ||||||
|  |  | ||||||
|         // 更新退款订单 |         // 更新退款订单 | ||||||
|  | |||||||
| @ -39,7 +39,7 @@ public interface PayClient { | |||||||
|      * @param reqDTO  统一退款请求信息 |      * @param reqDTO  统一退款请求信息 | ||||||
|      * @return 各支付渠道的统一返回结果 |      * @return 各支付渠道的统一返回结果 | ||||||
|      */ |      */ | ||||||
|     PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO); |     PayCommonResult<PayRefundUnifiedRespDTO> unifiedRefund(PayRefundUnifiedReqDTO reqDTO); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 解析支付退款通知数据 |      * 解析支付退款通知数据 | ||||||
|  | |||||||
| @ -43,7 +43,6 @@ public class PayRefundUnifiedReqDTO { | |||||||
|      */ |      */ | ||||||
|     private String payTradeNo; |     private String payTradeNo; | ||||||
|  |  | ||||||
|     // TODO @jason:这个字段,要不就使用 merchantRefundId,更直接 |  | ||||||
|     /** |     /** | ||||||
|      * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no |      * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no | ||||||
|      * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no |      * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no | ||||||
| @ -51,7 +50,7 @@ public class PayRefundUnifiedReqDTO { | |||||||
|      * 使用 商户的退款单号。{PayRefundDO 字段 merchantRefundNo} |      * 使用 商户的退款单号。{PayRefundDO 字段 merchantRefundNo} | ||||||
|      */ |      */ | ||||||
|     @NotEmpty(message = "退款请求单号") |     @NotEmpty(message = "退款请求单号") | ||||||
|     private String refundReqNo; |     private String merchantRefundId; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 退款原因 |      * 退款原因 | ||||||
|  | |||||||
| @ -18,22 +18,8 @@ import lombok.experimental.Accessors; | |||||||
| @Data | @Data | ||||||
| public class PayRefundUnifiedRespDTO { | public class PayRefundUnifiedRespDTO { | ||||||
|  |  | ||||||
|     // TODO @jason:可以合并下。退款处理中、成功,都是成功;其它就业务失败。这样,可以复用 PayCommonResult;这个 RespDTO 可以返回渠道的退款编号 |  | ||||||
|     /** |     /** | ||||||
|      * 渠道的退款结果 |      * 渠道退款单编号 | ||||||
|      */ |      */ | ||||||
|     private PayChannelRefundRespEnum channelResp; |     private String channelRefundId; | ||||||
|  |  | ||||||
|     // TODO @json:channelReturnCode 和 channelReturnMsg 放到 PayCommonResult 里噶 |  | ||||||
|     /** |  | ||||||
|      * 渠道返回码 |  | ||||||
|      */ |  | ||||||
|     private String channelCode; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 渠道返回信息 |  | ||||||
|      */ |  | ||||||
|     private String channelMsg; |  | ||||||
|  |  | ||||||
|     //TODO 退款资金渠 ??? |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -103,19 +103,19 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen | |||||||
|  |  | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) { |     public PayCommonResult<PayRefundUnifiedRespDTO> unifiedRefund(PayRefundUnifiedReqDTO reqDTO) { | ||||||
|         PayRefundUnifiedRespDTO resp; |         PayCommonResult<PayRefundUnifiedRespDTO> resp; | ||||||
|         try { |         try { | ||||||
|             resp = doUnifiedRefund(reqDTO); |             resp = doUnifiedRefund(reqDTO); | ||||||
|         }  catch (Throwable ex) { |         }  catch (Throwable ex) { | ||||||
|             // 记录异常日志 |             // 记录异常日志 | ||||||
|             log.error("[unifiedRefund][request({}) 发起退款失败]", toJsonString(reqDTO), ex); |             log.error("[unifiedRefund][request({}) 发起退款失败]", toJsonString(reqDTO), ex); | ||||||
|             throw exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR); |             resp = PayCommonResult.error(ex); | ||||||
|         } |         } | ||||||
|         return resp; |         return resp; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|     protected abstract PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable; |     protected abstract PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable; | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,9 +3,9 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; | |||||||
| import cn.hutool.core.bean.BeanUtil; | import cn.hutool.core.bean.BeanUtil; | ||||||
| import cn.hutool.core.date.DateUtil; | import cn.hutool.core.date.DateUtil; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping; | import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping; | ||||||
|  | import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.*; | import cn.iocoder.yudao.framework.pay.core.client.dto.*; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; | import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum; |  | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum; | import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum; | ||||||
| import com.alipay.api.AlipayApiException; | import com.alipay.api.AlipayApiException; | ||||||
| import com.alipay.api.AlipayConfig; | import com.alipay.api.AlipayConfig; | ||||||
| @ -100,39 +100,32 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl | |||||||
|      * @return 退款请求 Response |      * @return 退款请求 Response | ||||||
|      */ |      */ | ||||||
|     @Override |     @Override | ||||||
|     protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO)  { |     protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO)  { | ||||||
|         AlipayTradeRefundModel model=new AlipayTradeRefundModel(); |         AlipayTradeRefundModel model=new AlipayTradeRefundModel(); | ||||||
|         model.setTradeNo(reqDTO.getChannelOrderNo()); |         model.setTradeNo(reqDTO.getChannelOrderNo()); | ||||||
|         model.setOutTradeNo(reqDTO.getPayTradeNo()); |         model.setOutTradeNo(reqDTO.getPayTradeNo()); | ||||||
|         model.setOutRequestNo(reqDTO.getRefundReqNo()); |         model.setOutRequestNo(reqDTO.getMerchantRefundId()); | ||||||
|         model.setRefundAmount(calculateAmount(reqDTO.getAmount()).toString()); |         model.setRefundAmount(calculateAmount(reqDTO.getAmount()).toString()); | ||||||
|         model.setRefundReason(reqDTO.getReason()); |         model.setRefundReason(reqDTO.getReason()); | ||||||
|         AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest(); |         AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest(); | ||||||
|         refundRequest.setBizModel(model); |         refundRequest.setBizModel(model); | ||||||
|         PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO(); |  | ||||||
|         try { |         try { | ||||||
|             AlipayTradeRefundResponse response =  client.execute(refundRequest); |             AlipayTradeRefundResponse response =  client.execute(refundRequest); | ||||||
|             log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", toJsonString(response)); |             log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", toJsonString(response)); | ||||||
|             if (response.isSuccess()) { |             if (response.isSuccess()) { | ||||||
|                 //退款成功,更新为PROCESSING_NOTIFY, 而不是 SYNC_SUCCESS 通过支付宝回调接口处理。退款导致触发的异步通知, |  | ||||||
|                 //退款导致触发的异步通知是发送到支付接口中设置的notify_url |                 //退款导致触发的异步通知是发送到支付接口中设置的notify_url | ||||||
|                 //TODO 沙箱环境 返回 的tradeNo(渠道退款单号) 和 订单的tradNo 是一个值,是不是理解不对? |                 //支付宝不返回退款单号,设置为空 | ||||||
|                 respDTO.setChannelResp(PayChannelRefundRespEnum.SUCCESS) |                 PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO(); | ||||||
|                         .setChannelCode(response.getCode()) |                 respDTO.setChannelRefundId(""); | ||||||
|                         .setChannelMsg(response.getMsg()); |                 return PayCommonResult.build(response.getCode(), response.getMsg(), respDTO, codeMapping); | ||||||
|             }else{ |             }else{ | ||||||
|                 respDTO.setChannelResp(PayChannelRefundRespEnum.FAILURE) |                 //失败。需要抛出异常 | ||||||
|                         .setChannelCode(response.getSubCode()) |                 return PayCommonResult.build(response.getCode(), response.getMsg(), null, codeMapping); | ||||||
|                         .setChannelMsg(response.getSubMsg()); |  | ||||||
|             } |             } | ||||||
|             return respDTO; |  | ||||||
|         } catch (AlipayApiException e) { |         } catch (AlipayApiException e) { | ||||||
|             //TODO 记录异常日志 |             //TODO 记录异常日志 | ||||||
|             log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), e); |             log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), e); | ||||||
|             respDTO.setChannelResp(PayChannelRefundRespEnum.FAILURE) |             return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping); | ||||||
|                     .setChannelCode(e.getErrCode()) |  | ||||||
|                     .setChannelMsg(e.getErrMsg()); |  | ||||||
|             return respDTO; |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | |||||||
| @ -149,7 +149,7 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> { | |||||||
|  |  | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable { |     protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable { | ||||||
|         //TODO 需要实现 |         //TODO 需要实现 | ||||||
|         throw new UnsupportedOperationException(); |         throw new UnsupportedOperationException(); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -37,8 +37,8 @@ public class PayRefundController { | |||||||
|         PayRefundReqDTO req = PayRefundConvert.INSTANCE.convert(reqVO); |         PayRefundReqDTO req = PayRefundConvert.INSTANCE.convert(reqVO); | ||||||
|         req.setUserIp(getClientIP()); |         req.setUserIp(getClientIP()); | ||||||
|         //TODO 测试暂时模拟生成商户退款订单 |         //TODO 测试暂时模拟生成商户退款订单 | ||||||
|         if(StrUtil.isEmpty(reqVO.getMerchantRefundNo())) { |         if(StrUtil.isEmpty(reqVO.getMerchantRefundId())) { | ||||||
|             req.setMerchantRefundNo(PaySeqUtils.genMerchantRefundNo()); |             req.setMerchantRefundId(PaySeqUtils.genMerchantRefundNo()); | ||||||
|         } |         } | ||||||
|         return CommonResult.success( PayRefundConvert.INSTANCE.convert(payRefundCoreService.submitRefundOrder(req))); |         return CommonResult.success( PayRefundConvert.INSTANCE.convert(payRefundCoreService.submitRefundOrder(req))); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -29,6 +29,6 @@ public class PayRefundReqVO { | |||||||
|     @ApiModelProperty(value = "商户退款订单号", required = true, example = "MR202111180000000001") |     @ApiModelProperty(value = "商户退款订单号", required = true, example = "MR202111180000000001") | ||||||
|     //TODO 测试暂时模拟生成 |     //TODO 测试暂时模拟生成 | ||||||
|     //@NotEmpty(message = "商户退款订单号") |     //@NotEmpty(message = "商户退款订单号") | ||||||
|     private String merchantRefundNo; |     private String merchantRefundId; | ||||||
|  |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -15,23 +15,6 @@ import lombok.experimental.Accessors; | |||||||
| @AllArgsConstructor | @AllArgsConstructor | ||||||
| public class PayRefundRespVO { | public class PayRefundRespVO { | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 渠道返回结果 |  | ||||||
|      * 退款处理中和退款成功  返回  1 |  | ||||||
|      * 失败和其他情况 返回 2 |  | ||||||
|      */ |  | ||||||
|     private Integer channelReturnResult; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 渠道返回code |  | ||||||
|      */ |  | ||||||
|     private String channelReturnCode; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 渠道返回消息 |  | ||||||
|      */ |  | ||||||
|     private String  channelReturnMsg; |  | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 支付退款单编号, 自增 |      * 支付退款单编号, 自增 | ||||||
|      */ |      */ | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 jason
					jason