Преглед изворни кода

reactor:【PAY 支付】优化退款回调接口,增加 merchantRefundId 退款编号参数
reactor:【MALL 商城】售后退款时,基于回调处理

YunaiV пре 6 месеци
родитељ
комит
288d8e3132
41 измењених фајлова са 356 додато и 244 уклоњено
  1. 10 5
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java
  2. 3 1
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/aftersale/AfterSaleOperateTypeEnum.java
  3. 17 4
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/AfterSaleController.java
  4. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageWithdrawController.java
  5. 14 2
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/aop/AfterSaleLogAspect.java
  6. 5 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/utils/AfterSaleLogUtils.java
  7. 9 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java
  8. 81 37
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java
  9. 3 13
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java
  10. 8 0
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java
  11. 17 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java
  12. 6 0
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java
  13. 1 1
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java
  14. 15 0
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java
  15. 1 1
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java
  16. 1 1
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java
  17. 6 5
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java
  18. 3 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java
  19. 1 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.java
  20. 16 4
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java
  21. 0 45
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskBaseVO.java
  22. 1 13
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java
  23. 6 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java
  24. 39 5
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java
  25. 4 3
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java
  26. 0 43
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/notify/PayNotifyTaskConvert.java
  27. 0 3
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java
  28. 10 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java
  29. 1 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java
  30. 2 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyTaskMapper.java
  31. 2 2
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java
  32. 2 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderService.java
  33. 8 6
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java
  34. 2 2
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java
  35. 26 11
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java
  36. 1 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java
  37. 1 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java
  38. 1 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java
  39. 6 6
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeService.java
  40. 18 14
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java
  41. 8 8
      yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java

+ 10 - 5
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java

@@ -36,8 +36,10 @@ public interface ErrorCodeConstants {
     ErrorCode ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_031, "交易订单修改收货地址失败,原因:订单不是【待发货】状态");
     ErrorCode ORDER_CREATE_FAIL_EXIST_UNPAID = new ErrorCode(1_011_000_032, "交易订单创建失败,原因:存在未付款订单");
     ErrorCode ORDER_CANCEL_PAID_FAIL = new ErrorCode(1_011_000_033, "交易订单取消支付失败,原因:订单不是【{}】状态");
-    ErrorCode ORDER_PICK_UP_FAIL_NOT_VERIFY_USER = new ErrorCode(1_011_000_034, "交易订单自提失败,原因:你没有核销该门店订单的权限");
-    ErrorCode ORDER_CREATE_FAIL_INSUFFICIENT_USER_POINTS = new ErrorCode(1_011_000_035, "交易订单创建失败,原因:用户积分不足");
+    ErrorCode ORDER_UPDATE_PAID_ORDER_REFUNDED_FAIL_REFUND_NOT_FOUND = new ErrorCode(1_011_000_034, "交易订单更新支付订单退款状态失败,原因:退款单不存在");
+    ErrorCode ORDER_UPDATE_PAID_ORDER_REFUNDED_FAIL_REFUND_STATUS_NOT_SUCCESS = new ErrorCode(1_011_000_035, "交易订单更新支付订单退款状态失败,原因:退款单状态不是【退款成功】");
+    ErrorCode ORDER_PICK_UP_FAIL_NOT_VERIFY_USER = new ErrorCode(1_011_000_036, "交易订单自提失败,原因:你没有核销该门店订单的权限");
+    ErrorCode ORDER_CREATE_FAIL_INSUFFICIENT_USER_POINTS = new ErrorCode(1_011_000_037, "交易订单创建失败,原因:用户积分不足");
 
     // ========== After Sale 模块 1-011-000-100 ==========
     ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在");
@@ -51,9 +53,13 @@ public interface ErrorCodeConstants {
     ErrorCode AFTER_SALE_DELIVERY_FAIL_STATUS_NOT_SELLER_AGREE = new ErrorCode(1_011_000_108, "退货失败,售后单状态不处于【待买家退货】");
     ErrorCode AFTER_SALE_CONFIRM_FAIL_STATUS_NOT_BUYER_DELIVERY = new ErrorCode(1_011_000_109, "确认收货失败,售后单状态不处于【待确认收货】");
     ErrorCode AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND = new ErrorCode(1_011_000_110, "退款失败,售后单状态不是【待退款】");
+    ErrorCode AFTER_SALE_REFUND_FAIL_REFUND_NOT_FOUND = new ErrorCode(1_011_000_111, "退款失败,退款单不存在");
+    ErrorCode AFTER_SALE_REFUND_FAIL_REFUND_NOT_SUCCESS_OR_FAILURE = new ErrorCode(1_011_000_112, "退款失败,退款单未退款");
+    ErrorCode AFTER_SALE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(1_011_000_113, "退款失败,退款金额不匹配");
+    ErrorCode AFTER_SALE_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(1_011_000_114, "退款失败,退款单不匹配");
     ErrorCode AFTER_SALE_CANCEL_FAIL_STATUS_NOT_APPLY_OR_AGREE_OR_BUYER_DELIVERY =
-            new ErrorCode(1_011_000_111, "取消售后单失败,售后单状态不是【待审核】或【卖家同意】或【商家待收货】");
-    ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_COMBINATION_IN_PROGRESS = new ErrorCode(1_011_000_112, "订单拼团中,无法申请售后");
+            new ErrorCode(1_011_000_115, "取消售后单失败,售后单状态不是【待审核】或【卖家同意】或【商家待收货】");
+    ErrorCode AFTER_SALE_CREATE_FAIL_ORDER_STATUS_COMBINATION_IN_PROGRESS = new ErrorCode(1_011_000_116, "订单拼团中,无法申请售后");
 
     // ========== Cart 模块 1-011-002-000 ==========
     ErrorCode CARD_ITEM_NOT_FOUND = new ErrorCode(1_011_002_000, "购物车项不存在");
@@ -101,7 +107,6 @@ public interface ErrorCodeConstants {
     ErrorCode BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING = new ErrorCode(1_011_008_001, "佣金提现记录状态不是审核中");
     ErrorCode BROKERAGE_WITHDRAW_MIN_PRICE = new ErrorCode(1_011_008_002, "提现金额不能低于 {} 元");
     ErrorCode BROKERAGE_WITHDRAW_USER_BALANCE_NOT_ENOUGH = new ErrorCode(1_011_008_003, "您当前最多可提现 {} 元");
-    ErrorCode BROKERAGE_WITHDRAW_TRANSFER_FAIL_WECHAT_NOT_BIND = new ErrorCode(1_011_008_004, "提现失败,原因:用户未绑定微信");
     ErrorCode BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR = new ErrorCode(1_011_008_005, "提现单更新转账状态失败,转账单不匹配");
     ErrorCode BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_STATUS_NOT_SUCCESS_OR_CLOSED = new ErrorCode(1_011_008_006, "提现单更新转账状态失败,转账单状态不是成功或关闭状态");
     ErrorCode BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(1_011_008_007, "提现单更新转账状态失败,转账单金额不匹配");

+ 3 - 1
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/aftersale/AfterSaleOperateTypeEnum.java

@@ -19,7 +19,9 @@ public enum AfterSaleOperateTypeEnum {
     MEMBER_DELIVERY(20, "会员填写退货物流信息,快递公司:{deliveryName},快递单号:{logisticsNo}"),
     ADMIN_AGREE_RECEIVE(21, "商家收货"),
     ADMIN_DISAGREE_RECEIVE(22, "商家拒绝收货,原因:{reason}"),
-    ADMIN_REFUND(30, "商家退款"),
+    ADMIN_REFUND(30, "商家发起退款"),
+    SYSTEM_REFUND_SUCCESS(31, "退款成功"),
+    SYSTEM_REFUND_FAIL(32, "退款失败"),
     MEMBER_CANCEL(40, "会员取消退款"),
     ;
 

+ 17 - 4
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/aftersale/AfterSaleController.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.trade.controller.admin.aftersale;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
@@ -15,10 +16,12 @@ import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
 import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleLogService;
 import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService;
 import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
+import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
@@ -46,6 +49,8 @@ public class AfterSaleController {
     @Resource
     private TradeOrderQueryService tradeOrderQueryService;
     @Resource
+    private TradeOrderUpdateService tradeOrderUpdateService;
+    @Resource
     private AfterSaleLogService afterSaleLogService;
     @Resource
     private MemberUserApi memberUserApi;
@@ -133,11 +138,19 @@ public class AfterSaleController {
 
     @PostMapping("/update-refunded")
     @Operation(summary = "更新售后订单为已退款") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
-    @PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现
-    public CommonResult<Boolean> updateAfterRefund(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
-        // 目前业务逻辑,不需要做任何事情
-        // 当然,退款会有小概率会失败的情况,可以监控失败状态,进行告警
+    @PermitAll // 无需登录,安全由 AfterSaleService 内部校验实现
+    public CommonResult<Boolean> updateAfterSaleRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
         log.info("[updateAfterRefund][notifyReqDTO({})]", notifyReqDTO);
+        if (StrUtil.startWithAny(notifyReqDTO.getMerchantRefundId(), "order-")) {
+            tradeOrderUpdateService.updatePaidOrderRefunded(
+                    Long.parseLong(notifyReqDTO.getMerchantRefundId()),
+                    notifyReqDTO.getPayRefundId());
+        } else {
+            afterSaleService.updateAfterSaleRefunded(
+                    Long.parseLong(notifyReqDTO.getMerchantRefundId()),
+                    Long.parseLong(notifyReqDTO.getMerchantOrderId()),
+                    notifyReqDTO.getPayRefundId());
+        }
         return success(true);
     }
 

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageWithdrawController.java

@@ -88,7 +88,7 @@ public class BrokerageWithdrawController {
     public CommonResult<Boolean> updateBrokerageWithdrawTransferred(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) {
         log.info("[updateAfterRefund][notifyReqDTO({})]", notifyReqDTO);
         brokerageWithdrawService.updateBrokerageWithdrawTransferred(
-                Long.parseLong(notifyReqDTO.getMerchantOrderId()), notifyReqDTO.getPayTransferId());
+                Long.parseLong(notifyReqDTO.getMerchantTransferId()), notifyReqDTO.getPayTransferId());
         return success(true);
     }
 

+ 14 - 2
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/aop/AfterSaleLogAspect.java

@@ -1,18 +1,20 @@
 package cn.iocoder.yudao.module.trade.framework.aftersale.core.aop;
 
+import cn.hutool.core.util.ObjUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
 import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderLogDO;
+import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleOperateTypeEnum;
 import cn.iocoder.yudao.module.trade.framework.aftersale.core.annotations.AfterSaleLog;
 import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleLogService;
 import cn.iocoder.yudao.module.trade.service.aftersale.bo.AfterSaleLogCreateReqBO;
+import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.annotation.AfterReturning;
 import org.aspectj.lang.annotation.Aspect;
 
-import jakarta.annotation.Resource;
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
@@ -50,6 +52,10 @@ public class AfterSaleLogAspect {
      * 操作后的状态
      */
     private static final ThreadLocal<Integer> AFTER_STATUS = new ThreadLocal<>();
+    /**
+     * 操作类型(仅“动态场景”需要使用)
+     */
+    private static final ThreadLocal<AfterSaleOperateTypeEnum> OPERATE_TYPE = new ThreadLocal<>();
     /**
      * 拓展参数 Map,用于格式化操作内容
      */
@@ -69,6 +75,7 @@ public class AfterSaleLogAspect {
             if (afterSaleId == null) { // 如果未设置,只有注解,说明不需要记录日志
                 return;
             }
+            AfterSaleOperateTypeEnum operateType = ObjUtil.defaultIfNull(OPERATE_TYPE.get(), afterSaleLog.operateType());
             Integer beforeStatus = BEFORE_STATUS.get();
             Integer afterStatus = AFTER_STATUS.get();
             Map<String, Object> exts = ObjectUtil.defaultIfNull(EXTS.get(), emptyMap());
@@ -78,7 +85,7 @@ public class AfterSaleLogAspect {
             AfterSaleLogCreateReqBO createBO = new AfterSaleLogCreateReqBO()
                     .setUserId(userId).setUserType(userType)
                     .setAfterSaleId(afterSaleId).setBeforeStatus(beforeStatus).setAfterStatus(afterStatus)
-                    .setOperateType(afterSaleLog.operateType().getType()).setContent(content);
+                    .setOperateType(operateType.getType()).setContent(content);
             afterSaleLogService.createAfterSaleLog(createBO);
         } catch (Exception exception) {
             log.error("[doAfterReturning][afterSaleLog({}) 日志记录错误]", toJsonString(afterSaleLog), exception);
@@ -116,6 +123,10 @@ public class AfterSaleLogAspect {
         EXTS.set(exts);
     }
 
+    public static void setAfterSaleOperateType(AfterSaleOperateTypeEnum operateType) {
+        OPERATE_TYPE.set(operateType);
+    }
+
     public static void setUserInfo(Long userId, Integer userType) {
         USER_ID.set(userId);
         USER_TYPE.set(userType);
@@ -127,6 +138,7 @@ public class AfterSaleLogAspect {
         AFTER_SALE_ID.remove();
         BEFORE_STATUS.remove();
         AFTER_STATUS.remove();
+        OPERATE_TYPE.remove();
         EXTS.remove();
     }
 

+ 5 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/framework/aftersale/core/utils/AfterSaleLogUtils.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.trade.framework.aftersale.core.utils;
 
 
+import cn.iocoder.yudao.module.trade.enums.aftersale.AfterSaleOperateTypeEnum;
 import cn.iocoder.yudao.module.trade.framework.aftersale.core.aop.AfterSaleLogAspect;
 
 import java.util.Map;
@@ -13,6 +14,10 @@ import java.util.Map;
  */
 public class AfterSaleLogUtils {
 
+    public static void setAfterSaleOperateType(AfterSaleOperateTypeEnum operateType) {
+        AfterSaleLogAspect.setAfterSaleOperateType(operateType);
+    }
+
     public static void setAfterSaleInfo(Long id, Integer beforeStatus, Integer afterStatus) {
         setAfterSaleInfo(id, beforeStatus, afterStatus, null);
     }

+ 9 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleService.java

@@ -108,6 +108,15 @@ public interface AfterSaleService {
      */
     void refundAfterSale(Long userId, String userIp, Long id);
 
+    /**
+     * 更新售后订单为已退款
+     *
+     * @param id          售后编号
+     * @param orderId     订单编号
+     * @param payRefundId 支付退款编号
+     */
+    void updateAfterSaleRefunded(Long id, Long orderId, Long payRefundId);
+
     /**
      * 【会员】取消售后
      *

+ 81 - 37
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/aftersale/AfterSaleServiceImpl.java

@@ -7,6 +7,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
+import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
+import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
 import cn.iocoder.yudao.module.promotion.api.combination.CombinationRecordApi;
 import cn.iocoder.yudao.module.promotion.api.combination.dto.CombinationRecordRespDTO;
 import cn.iocoder.yudao.module.promotion.enums.combination.CombinationRecordStatusEnum;
@@ -41,13 +43,12 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
-import org.springframework.transaction.support.TransactionSynchronization;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
 import org.springframework.validation.annotation.Validated;
 
 import java.time.LocalDateTime;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
 
 /**
@@ -184,8 +185,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
         // 记录售后日志
         AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), null,
                 AfterSaleStatusEnum.APPLY.getStatus());
-
-        // TODO 发送售后消息
         return afterSale;
     }
 
@@ -206,8 +205,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
 
         // 记录售后日志
         AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), newStatus);
-
-        // TODO 发送售后消息
     }
 
     @Override
@@ -226,8 +223,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
         // 记录售后日志
         AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), newStatus);
 
-        // TODO 发送售后消息
-
         // 更新交易订单项的售后状态为【未申请】
         tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId());
     }
@@ -281,8 +276,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
                 AfterSaleStatusEnum.BUYER_DELIVERY.getStatus(),
                 MapUtil.<String, Object>builder().put("deliveryName", express.getName())
                         .put("logisticsNo", deliveryReqVO.getLogisticsNo()).build());
-
-        // TODO 发送售后消息
     }
 
     @Override
@@ -299,8 +292,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
         // 记录售后日志
         AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(),
                 AfterSaleStatusEnum.WAIT_REFUND.getStatus());
-
-        // TODO 发送售后消息
     }
 
     @Override
@@ -326,8 +317,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
                 AfterSaleStatusEnum.SELLER_REFUSE.getStatus(),
                 MapUtil.of("reason", refuseReqVO.getRefuseMemo()));
 
-        // TODO 发送售后消息
-
         // 更新交易订单项的售后状态为【未申请】
         tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId());
     }
@@ -365,33 +354,90 @@ public class AfterSaleServiceImpl implements AfterSaleService {
         // 发起退款单。注意,需要在事务提交后,再进行发起,避免重复发起
         createPayRefund(userIp, afterSale);
 
-        // 更新售后单的状态为【已完成】
-        updateAfterSaleStatus(afterSale.getId(), AfterSaleStatusEnum.WAIT_REFUND.getStatus(), new AfterSaleDO()
-                .setStatus(AfterSaleStatusEnum.COMPLETE.getStatus()).setRefundTime(LocalDateTime.now()));
-
         // 记录售后日志
         AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(),
-                AfterSaleStatusEnum.COMPLETE.getStatus());
+                afterSale.getStatus()); // 特殊:这里状态不变,而是最终 updateAfterSaleRefunded 处理!!!
+    }
 
-        // TODO 发送售后消息
+    private void createPayRefund(String userIp, AfterSaleDO afterSale) {
+        // 创建退款单
+        PayRefundCreateReqDTO createReqDTO = AfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties)
+                .setReason(StrUtil.format("退款【{}】", afterSale.getSpuName()));
+        Long payRefundId = payRefundApi.createRefund(createReqDTO);
 
-        // 更新交易订单项的售后状态为【已完成】
-        tradeOrderUpdateService.updateOrderItemWhenAfterSaleSuccess(afterSale.getOrderItemId(), afterSale.getRefundPrice());
+        // 更新售后单的退款单号
+        tradeAfterSaleMapper.updateById(new AfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId));
     }
 
-    private void createPayRefund(String userIp, AfterSaleDO afterSale) {
-        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
-
-            @Override
-            public void afterCommit() {
-                // 创建退款单
-                PayRefundCreateReqDTO createReqDTO = AfterSaleConvert.INSTANCE.convert(userIp, afterSale, tradeOrderProperties)
-                        .setReason(StrUtil.format("退款【{}】", afterSale.getSpuName()));
-                Long payRefundId = payRefundApi.createRefund(createReqDTO);
-                // 更新售后单的退款单号
-                tradeAfterSaleMapper.updateById(new AfterSaleDO().setId(afterSale.getId()).setPayRefundId(payRefundId));
-            }
-        });
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    @AfterSaleLog(operateType = AfterSaleOperateTypeEnum.SYSTEM_REFUND_SUCCESS)
+    public void updateAfterSaleRefunded(Long id, Long orderId, Long payRefundId) {
+        // 1. 校验售后单的状态,并状态待退款
+        AfterSaleDO afterSale = tradeAfterSaleMapper.selectById(id);
+        if (afterSale == null) {
+            throw exception(AFTER_SALE_NOT_FOUND);
+        }
+        if (ObjectUtil.notEqual(afterSale.getStatus(), AfterSaleStatusEnum.WAIT_REFUND.getStatus())) {
+            throw exception(AFTER_SALE_REFUND_FAIL_STATUS_NOT_WAIT_REFUND);
+        }
+
+        // 2. 校验退款单
+        PayRefundRespDTO payRefund = validatePayRefund(afterSale, payRefundId);
+
+        // 3. 处理退款结果
+        if (PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
+            // 【情况一:退款成功】
+            updateAfterSaleStatus(afterSale.getId(), AfterSaleStatusEnum.WAIT_REFUND.getStatus(), new AfterSaleDO()
+                .setStatus(AfterSaleStatusEnum.COMPLETE.getStatus()).setRefundTime(LocalDateTime.now()));
+
+            // 记录售后日志
+            AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), AfterSaleStatusEnum.COMPLETE.getStatus());
+
+            // 更新交易订单项的售后状态为【已完成】
+            tradeOrderUpdateService.updateOrderItemWhenAfterSaleSuccess(afterSale.getOrderItemId(), afterSale.getRefundPrice());
+            // 【情况二:退款失败】
+        } else if (PayRefundStatusEnum.isFailure(payRefund.getStatus())) {
+            // 记录售后日志
+            AfterSaleLogUtils.setAfterSaleOperateType(AfterSaleOperateTypeEnum.SYSTEM_REFUND_FAIL);
+            AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(), afterSale.getStatus());
+        }
+    }
+
+    /**
+     * 校验退款单的合法性
+     *
+     * @param afterSale 售后单
+     * @param payRefundId 退款单编号
+     * @return 退款单
+     */
+    private PayRefundRespDTO validatePayRefund(AfterSaleDO afterSale, Long payRefundId) {
+        // 1. 校验退款单是否存在
+        PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId);
+        if (payRefund == null) {
+            log.error("[validatePayRefund][afterSale({}) payRefund({}) 不存在,请进行处理!]", afterSale.getId(), payRefundId);
+            throw exception(AFTER_SALE_REFUND_FAIL_REFUND_NOT_FOUND);
+        }
+        // 2.1 校验退款单无退款结果(成功、失败)
+        if (!PayRefundStatusEnum.isSuccess(payRefund.getStatus())
+            && !PayRefundStatusEnum.isFailure(payRefund.getStatus())) {
+            log.error("[validatePayRefund][afterSale({}) payRefund({}) 无退款结果,请进行处理!payRefund 数据是:{}]",
+                    afterSale.getId(), payRefundId, toJsonString(payRefund));
+            throw exception(AFTER_SALE_REFUND_FAIL_REFUND_NOT_SUCCESS_OR_FAILURE);
+        }
+        // 2.2 校验退款金额一致
+        if (ObjectUtil.notEqual(payRefund.getRefundPrice(), afterSale.getRefundPrice())) {
+            log.error("[validatePayRefund][afterSale({}) payRefund({}) 退款金额不匹配,请进行处理!afterSale 数据是:{},payRefund 数据是:{}]",
+                    afterSale.getId(), payRefundId, toJsonString(afterSale), toJsonString(payRefund));
+            throw exception(AFTER_SALE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
+        }
+        // 2.3 校验退款订单匹配(二次)
+        if (ObjectUtil.notEqual(payRefund.getMerchantRefundId(), afterSale.getId().toString())) {
+            log.error("[validatePayRefund][afterSale({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]",
+                    afterSale.getId(), payRefundId, toJsonString(payRefund));
+            throw exception(AFTER_SALE_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
+        }
+        return payRefund;
     }
 
     @Override
@@ -417,8 +463,6 @@ public class AfterSaleServiceImpl implements AfterSaleService {
         AfterSaleLogUtils.setAfterSaleInfo(afterSale.getId(), afterSale.getStatus(),
                 AfterSaleStatusEnum.BUYER_CANCEL.getStatus());
 
-        // TODO 发送售后消息
-
         // 更新交易订单项的售后状态为【未申请】
         tradeOrderUpdateService.updateOrderItemWhenAfterSaleCancel(afterSale.getOrderItemId());
     }

+ 3 - 13
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java

@@ -15,9 +15,6 @@ import cn.iocoder.yudao.module.pay.api.wallet.PayWalletApi;
 import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
 import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
 import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
-import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
-import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
-import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawPageReqVO;
 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawCreateReqVO;
 import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageWithdrawConvert;
@@ -71,8 +68,6 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
     @Resource
     private PayTransferApi payTransferApi;
     @Resource
-    private SocialUserApi socialUserApi;
-    @Resource
     private PayWalletApi payWalletApi;
 
     @Resource
@@ -140,13 +135,8 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
         if (Objects.equal(withdraw.getType(), BrokerageWithdrawTypeEnum.ALIPAY_API.getType())) {
             channelCode = PayChannelEnum.ALIPAY_PC.getCode();
         } else if (Objects.equal(withdraw.getType(), BrokerageWithdrawTypeEnum.WECHAT_API.getType())) {
-            SocialUserRespDTO socialUser = socialUserApi.getSocialUserByUserId(
-                    UserTypeEnum.MEMBER.getValue(), withdraw.getUserId(), SocialTypeEnum.WECHAT_MINI_PROGRAM.getType());
-            if (socialUser == null) {
-                throw exception(BROKERAGE_WITHDRAW_TRANSFER_FAIL_WECHAT_NOT_BIND);
-            }
             channelCode = PayChannelEnum.WX_LITE.getCode();
-            userAccount = socialUser.getOpenid();
+            userAccount = withdraw.getUserAccount();
             // 特殊:微信需要有报备信息
             channelExtras = PayTransferCreateReqDTO.buildWeiXinChannelExtra1000("佣金提现", "佣金提现");
         } else if (Objects.equal(withdraw.getType(), BrokerageWithdrawTypeEnum.WALLET.getType())) {
@@ -158,7 +148,7 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
         // 1.2 构建请求
         PayTransferCreateReqDTO transferReqDTO = new PayTransferCreateReqDTO()
                 .setAppKey(tradeOrderProperties.getPayAppKey()).setChannelCode(channelCode)
-                .setMerchantOrderId(withdraw.getId().toString()).setSubject("佣金提现").setPrice(withdraw.getPrice())
+                .setMerchantTransferId(withdraw.getId().toString()).setSubject("佣金提现").setPrice(withdraw.getPrice())
                 .setUserAccount(userAccount).setUserName(userName).setUserIp(getClientIP())
                 .setChannelExtras(channelExtras);
         // 1.3 发起请求
@@ -293,7 +283,7 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
             throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH);
         }
         // 2.3 校验转账订单匹配
-        if (ObjectUtil.notEqual(payTransfer.getMerchantOrderId(), withdraw.getId().toString())) {
+        if (ObjectUtil.notEqual(payTransfer.getMerchantTransferId(), withdraw.getId().toString())) {
             log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) 转账单不匹配({}),请进行处理!payTransfer 数据是:{}]",
                     withdraw.getId(), payTransferId, JsonUtils.toJsonString(payTransfer));
             throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_MERCHANT_EXISTS);

+ 8 - 0
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateService.java

@@ -209,6 +209,14 @@ public interface TradeOrderUpdateService {
      */
     void cancelPaidOrder(Long userId, Long orderId, Integer cancelType);
 
+    /**
+     * 取消支付订单的退款回调
+     *
+     * @param id               订单编号
+     * @param payRefundId      支付退款编号
+     */
+    void updatePaidOrderRefunded(Long id, Long payRefundId);
+
     /**
      * 更新下单赠送的优惠券编号到订单
      *

+ 17 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/order/TradeOrderUpdateServiceImpl.java

@@ -20,7 +20,9 @@ import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
 import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
+import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
+import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
 import cn.iocoder.yudao.module.product.api.comment.ProductCommentApi;
 import cn.iocoder.yudao.module.product.api.comment.dto.ProductCommentCreateReqDTO;
 import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
@@ -938,10 +940,24 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
                 .setAppKey(tradeOrderProperties.getPayAppKey())  // 支付应用
                 .setUserIp(NetUtil.getLocalhostStr()) // 使用本机 IP,因为是服务器发起退款的
                 .setMerchantOrderId(String.valueOf(order.getId())) // 支付单号
-                .setMerchantRefundId(String.valueOf(order.getId()))
+                // 特殊:因为订单支持 AfterSale 单个售后退款,也支持整单退款,所以需要通过 order- 进行下区分
+                //      具体可见 AfterSaleController 的 updateAfterSaleRefunded 方法
+                .setMerchantRefundId("order-" + order.getId())
                 .setReason(TradeOrderCancelTypeEnum.COMBINATION_CLOSE.getName()).setPrice(order.getPayPrice())); // 价格信息
     }
 
+    @Override
+    public void updatePaidOrderRefunded(Long id, Long payRefundId) {
+        PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId);
+        if (payRefund == null) {
+            throw exception(ORDER_UPDATE_PAID_ORDER_REFUNDED_FAIL_REFUND_NOT_FOUND);
+        }
+        // 特殊:因为在 cancelPaidOrder 已经进行订单的取消,所以这里必须退款成功!!!
+        if (!PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
+            throw exception(ORDER_UPDATE_PAID_ORDER_REFUNDED_FAIL_REFUND_STATUS_NOT_SUCCESS);
+        }
+    }
+
     @Override
     public void updateOrderGiveCouponIds(Long userId, Long orderId, List<Long> giveCouponIds) {
         // 1. 检验订单存在

+ 6 - 0
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java

@@ -25,6 +25,12 @@ public class PayRefundNotifyReqDTO {
     @NotEmpty(message = "商户退款单编号不能为空")
     private String merchantOrderId;
 
+    /**
+     * 商户退款编号
+     */
+    @NotEmpty(message = "商户退款编号不能为空")
+    private String merchantRefundId;
+
     /**
      * 支付退款编号
      */

+ 1 - 1
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java

@@ -23,7 +23,7 @@ public class PayTransferNotifyReqDTO {
      * 商户转账单号
      */
     @NotEmpty(message = "商户转账单号不能为空")
-    private String merchantOrderId;
+    private String merchantTransferId;
 
     /**
      * 转账订单编号

+ 15 - 0
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java

@@ -42,9 +42,24 @@ public class PayRefundRespDTO {
      * 商户订单编号
      */
     private String merchantOrderId;
+    /**
+     * 商户退款编号
+     */
+    private String merchantRefundId;
     /**
      * 退款成功时间
      */
     private LocalDateTime successTime;
 
+    // ========== 渠道相关字段 ==========
+
+    /**
+     * 调用渠道的错误码
+     */
+    private String channelErrorCode;
+    /**
+     * 调用渠道的错误提示
+     */
+    private String channelErrorMsg;
+
 }

+ 1 - 1
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java

@@ -47,7 +47,7 @@ public class PayTransferCreateReqDTO {
      * 商户转账单编号
      */
     @NotEmpty(message = "商户转账单编号能为空")
-    private String merchantOrderId;
+    private String merchantTransferId;
 
     /**
      * 转账金额,单位:分

+ 1 - 1
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java

@@ -31,7 +31,7 @@ public class PayTransferRespDTO {
     /**
      * 商户转账单编号
      */
-    private String merchantOrderId;
+    private String merchantTransferId;
 
     // ========== 转账相关字段 ==========
 

+ 6 - 5
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java

@@ -1,14 +1,14 @@
 package cn.iocoder.yudao.module.pay.api.refund;
 
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
-import cn.iocoder.yudao.module.pay.convert.refund.PayRefundConvert;
+import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
 import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
+import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
-import jakarta.annotation.Resource;
-
 /**
  * 退款单 API 实现类
  *
@@ -23,12 +23,13 @@ public class PayRefundApiImpl implements PayRefundApi {
 
     @Override
     public Long createRefund(PayRefundCreateReqDTO reqDTO) {
-        return payRefundService.createPayRefund(reqDTO);
+        return payRefundService.createRefund(reqDTO);
     }
 
     @Override
     public PayRefundRespDTO getRefund(Long id) {
-        return PayRefundConvert.INSTANCE.convert02(payRefundService.getRefund(id));
+        PayRefundDO refund = payRefundService.getRefund(id);
+        return BeanUtils.toBean(refund, PayRefundRespDTO.class);
     }
 
 }

+ 3 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java

@@ -66,7 +66,9 @@ public class PayDemoOrderController {
     @Operation(summary = "更新示例订单为已退款") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
     @PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现
     public CommonResult<Boolean> updateDemoOrderRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
-        payDemoOrderService.updateDemoOrderRefunded(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
+        payDemoOrderService.updateDemoOrderRefunded(
+                Long.valueOf(notifyReqDTO.getMerchantOrderId()),
+                notifyReqDTO.getMerchantRefundId(),
                 notifyReqDTO.getPayRefundId());
         return success(true);
     }

+ 1 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.java

@@ -55,7 +55,7 @@ public class PayDemoWithdrawController {
     @Operation(summary = "更新示例提现单的转账状态") // 由 pay-module 转账服务,进行回调
     @PermitAll // 无需登录,安全由 PayDemoTransferService 内部校验实现
     public CommonResult<Boolean> updateDemoWithdrawTransferred(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) {
-        demoWithdrawService.updateDemoWithdrawTransferred(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
+        demoWithdrawService.updateDemoWithdrawTransferred(Long.valueOf(notifyReqDTO.getMerchantTransferId()),
                 notifyReqDTO.getPayTransferId());
         return success(true);
     }

+ 16 - 4
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java

@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.pay.controller.admin.notify;
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
@@ -11,7 +12,6 @@ import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO;
 import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskRespVO;
-import cn.iocoder.yudao.module.pay.convert.notify.PayNotifyTaskConvert;
 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
@@ -138,7 +138,12 @@ public class PayNotifyController {
         // 拼接返回
         PayAppDO app = appService.getApp(task.getAppId());
         List<PayNotifyLogDO> logs = notifyService.getNotifyLogList(id);
-        return success(PayNotifyTaskConvert.INSTANCE.convert(task, app, logs));
+        return success(BeanUtils.toBean(task, PayNotifyTaskDetailRespVO.class, respVO -> {
+            if (app != null) {
+                respVO.setAppName(app.getName());
+            }
+            respVO.setLogs(BeanUtils.toBean(logs, PayNotifyTaskDetailRespVO.Log.class));
+        }));
     }
 
     @GetMapping("/page")
@@ -150,8 +155,15 @@ public class PayNotifyController {
             return success(PageResult.empty());
         }
         // 拼接返回
-        Map<Long, PayAppDO> appMap = appService.getAppMap(convertList(pageResult.getList(), PayNotifyTaskDO::getAppId));
-        return success(PayNotifyTaskConvert.INSTANCE.convertPage(pageResult, appMap));
+        Map<Long, PayAppDO> apps = appService.getAppMap(convertList(pageResult.getList(), PayNotifyTaskDO::getAppId));
+        
+        // 转换对象
+        return success(BeanUtils.toBean(pageResult, PayNotifyTaskRespVO.class, order -> {
+            PayAppDO app = apps.get(order.getAppId());
+            if (app != null) {
+                order.setAppName(app.getName());
+            }
+        }));
     }
 
 }

+ 0 - 45
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskBaseVO.java

@@ -1,45 +0,0 @@
-package cn.iocoder.yudao.module.pay.controller.admin.notify.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import java.time.LocalDateTime;
-
-/**
- * 回调通知 Base VO,提供给添加、修改、详细的子 VO 使用
- * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成
- */
-@Data
-public class PayNotifyTaskBaseVO {
-
-    @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10636")
-    private Long appId;
-
-    @Schema(description = "通知类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
-    private Byte type;
-
-    @Schema(description = "数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6722")
-    private Long dataId;
-
-    @Schema(description = "通知状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
-    private Byte status;
-
-    @Schema(description = "商户订单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "26697")
-    private String merchantOrderId;
-
-    @Schema(description = "下一次通知时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    private LocalDateTime nextNotifyTime;
-
-    @Schema(description = "最后一次执行时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    private LocalDateTime lastExecuteTime;
-
-    @Schema(description = "当前通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
-    private Byte notifyTimes;
-
-    @Schema(description = "最大可通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
-    private Byte maxNotifyTimes;
-
-    @Schema(description = "异步通知地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
-    private String notifyUrl;
-
-}

+ 1 - 13
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskDetailRespVO.java

@@ -13,19 +13,7 @@ import java.util.List;
 @Data
 @EqualsAndHashCode(callSuper = true)
 @ToString(callSuper = true)
-public class PayNotifyTaskDetailRespVO extends PayNotifyTaskBaseVO {
-
-    @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3380")
-    private Long id;
-
-    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    private LocalDateTime createTime;
-
-    @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    private LocalDateTime updateTime;
-
-    @Schema(description = "应用名称", example = "wx_pay")
-    private String appName;
+public class PayNotifyTaskDetailRespVO extends PayNotifyTaskRespVO {
 
     @Schema(description = "回调日志列表")
     private List<Log> logs;

+ 6 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskPageReqVO.java

@@ -32,6 +32,12 @@ public class PayNotifyTaskPageReqVO extends PageParam {
     @Schema(description = "商户订单编号", example = "26697")
     private String merchantOrderId;
 
+    @Schema(description = "商户退款编号", example = "26697")
+    private String merchantRefundId;
+
+    @Schema(description = "商户转账编号", example = "26697")
+    private String merchantTransferId;
+
     @Schema(description = "创建时间")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime[] createTime;

+ 39 - 5
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/vo/PayNotifyTaskRespVO.java

@@ -6,17 +6,51 @@ import java.time.LocalDateTime;
 
 @Schema(description = "管理后台 - 回调通知 Response VO")
 @Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class PayNotifyTaskRespVO extends PayNotifyTaskBaseVO {
+public class PayNotifyTaskRespVO {
 
     @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3380")
     private Long id;
 
-    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
-    private LocalDateTime createTime;
+    @Schema(description = "应用编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10636")
+    private Long appId;
 
     @Schema(description = "应用名称", example = "wx_pay")
     private String  appName;
 
+    @Schema(description = "通知类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+    private Byte type;
+
+    @Schema(description = "数据编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "6722")
+    private Long dataId;
+
+    @Schema(description = "商户订单编号", example = "26697")
+    private String merchantOrderId;
+
+    @Schema(description = "商户退款编号", example = "26697")
+    private String merchantRefundId;
+
+    @Schema(description = "商户转账编号", example = "26697")
+    private String merchantTransferId;
+
+    @Schema(description = "通知状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Byte status;
+
+    @Schema(description = "下一次通知时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime nextNotifyTime;
+
+    @Schema(description = "最后一次执行时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime lastExecuteTime;
+
+    @Schema(description = "当前通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Byte notifyTimes;
+
+    @Schema(description = "最大可通知次数", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Byte maxNotifyTimes;
+
+    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+    @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime updateTime;
+
 }

+ 4 - 3
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java

@@ -37,8 +37,7 @@ public class PayWalletRechargeController {
         return success(true);
     }
 
-    // TODO @jason:发起退款,要 post 操作哈;
-    @GetMapping("/refund")
+    @PostMapping("/refund")
     @Operation(summary = "发起钱包充值退款")
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
     public CommonResult<Boolean> refundWalletRecharge(@RequestParam("id") Long id) {
@@ -51,7 +50,9 @@ public class PayWalletRechargeController {
     @PermitAll // 无需登录, 内部校验实现
     public CommonResult<Boolean> updateWalletRechargeRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
         walletRechargeService.updateWalletRechargeRefunded(
-                Long.valueOf(notifyReqDTO.getMerchantOrderId()), notifyReqDTO.getPayRefundId());
+                Long.valueOf(notifyReqDTO.getMerchantOrderId()),
+                Long.valueOf(notifyReqDTO.getMerchantRefundId()),
+                notifyReqDTO.getPayRefundId());
         return success(true);
     }
 

+ 0 - 43
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/notify/PayNotifyTaskConvert.java

@@ -1,43 +0,0 @@
-package cn.iocoder.yudao.module.pay.convert.notify;
-
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
-import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO;
-import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskRespVO;
-import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
-import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
-import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
-import org.mapstruct.Mapper;
-import org.mapstruct.factory.Mappers;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * 支付通知 Convert
- *
- * @author 芋道源码
- */
-@Mapper
-public interface PayNotifyTaskConvert {
-
-    PayNotifyTaskConvert INSTANCE = Mappers.getMapper(PayNotifyTaskConvert.class);
-
-    PayNotifyTaskRespVO convert(PayNotifyTaskDO bean);
-
-    default PageResult<PayNotifyTaskRespVO> convertPage(PageResult<PayNotifyTaskDO> page, Map<Long, PayAppDO> appMap){
-        PageResult<PayNotifyTaskRespVO> result = convertPage(page);
-        result.getList().forEach(order -> MapUtils.findAndThen(appMap, order.getAppId(), app -> order.setAppName(app.getName())));
-        return result;
-    }
-    PageResult<PayNotifyTaskRespVO> convertPage(PageResult<PayNotifyTaskDO> page);
-
-    default PayNotifyTaskDetailRespVO convert(PayNotifyTaskDO task, PayAppDO app, List<PayNotifyLogDO> logs) {
-        PayNotifyTaskDetailRespVO respVO = convert(task, logs);
-        if (app != null) {
-            respVO.setAppName(app.getName());
-        }
-        return respVO;
-    }
-    PayNotifyTaskDetailRespVO convert(PayNotifyTaskDO task, List<PayNotifyLogDO> logs);
-}

+ 0 - 3
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java

@@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
-import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundDetailsRespVO;
 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExcelVO;
 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageItemRespVO;
@@ -42,8 +41,6 @@ public interface PayRefundConvert {
 
     PayRefundDO convert(PayRefundCreateReqDTO bean);
 
-    PayRefundRespDTO convert02(PayRefundDO bean);
-
     default List<PayRefundExcelVO> convertList(List<PayRefundDO> list, Map<Long, PayAppDO> appMap) {
         return CollectionUtils.convertList(list, order -> {
             PayRefundExcelVO excelVO = convertExcel(order);

+ 10 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/notify/PayNotifyTaskDO.java

@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum;
 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
@@ -60,12 +61,21 @@ public class PayNotifyTaskDO extends TenantBaseDO {
      *
      * 1. {@link PayNotifyTypeEnum#ORDER} 时,关联 {@link PayOrderDO#getId()}
      * 2. {@link PayNotifyTypeEnum#REFUND} 时,关联 {@link PayRefundDO#getId()}
+     * 3. {@link PayNotifyTypeEnum#TRANSFER} 时,关联 {@link PayTransferDO#getId()}
      */
     private Long dataId;
     /**
      * 商户订单编号
      */
     private String merchantOrderId;
+    /**
+     * 商户退款编号
+     */
+    private String merchantRefundId;
+    /**
+     * 商户转账编号
+     */
+    private String merchantTransferId;
     /**
      * 通知状态
      *

+ 1 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java

@@ -63,7 +63,7 @@ public class PayTransferDO extends BaseDO {
      *
      * 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一
      */
-    private String merchantOrderId;
+    private String merchantTransferId;
 
     // ========== 转账相关字段 ==========
 

+ 2 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyTaskMapper.java

@@ -37,6 +37,8 @@ public interface PayNotifyTaskMapper extends BaseMapperX<PayNotifyTaskDO> {
                 .eqIfPresent(PayNotifyTaskDO::getDataId, reqVO.getDataId())
                 .eqIfPresent(PayNotifyTaskDO::getStatus, reqVO.getStatus())
                 .eqIfPresent(PayNotifyTaskDO::getMerchantOrderId, reqVO.getMerchantOrderId())
+                .eqIfPresent(PayNotifyTaskDO::getMerchantRefundId, reqVO.getMerchantRefundId())
+                .eqIfPresent(PayNotifyTaskDO::getMerchantTransferId, reqVO.getMerchantTransferId())
                 .betweenIfPresent(PayNotifyTaskDO::getCreateTime, reqVO.getCreateTime())
                 .orderByDesc(PayNotifyTaskDO::getId));
     }

+ 2 - 2
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java

@@ -28,7 +28,7 @@ public interface PayTransferMapper extends BaseMapperX<PayTransferDO> {
 
     default PayTransferDO selectByAppIdAndMerchantOrderId(Long appId, String merchantOrderId) {
         return selectOne(PayTransferDO::getAppId, appId,
-                    PayTransferDO::getMerchantOrderId, merchantOrderId);
+                    PayTransferDO::getMerchantTransferId, merchantOrderId);
     }
 
     default PageResult<PayTransferDO> selectPage(PayTransferPageReqVO reqVO) {
@@ -36,7 +36,7 @@ public interface PayTransferMapper extends BaseMapperX<PayTransferDO> {
                 .eqIfPresent(PayTransferDO::getNo, reqVO.getNo())
                 .eqIfPresent(PayTransferDO::getAppId, reqVO.getAppId())
                 .eqIfPresent(PayTransferDO::getChannelCode, reqVO.getChannelCode())
-                .eqIfPresent(PayTransferDO::getMerchantOrderId, reqVO.getMerchantOrderId())
+                .eqIfPresent(PayTransferDO::getMerchantTransferId, reqVO.getMerchantOrderId())
                 .eqIfPresent(PayTransferDO::getStatus, reqVO.getStatus())
                 .likeIfPresent(PayTransferDO::getUserName, reqVO.getUserName())
                 .eqIfPresent(PayTransferDO::getChannelTransferNo, reqVO.getChannelTransferNo())

+ 2 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderService.java

@@ -59,8 +59,9 @@ public interface PayDemoOrderService {
      * 更新示例订单为已退款
      *
      * @param id 编号
+     * @param refundId 退款编号
      * @param payRefundId 退款订单号
      */
-    void updateDemoOrderRefunded(Long id, Long payRefundId);
+    void updateDemoOrderRefunded(Long id, String refundId, Long payRefundId);
 
 }

+ 8 - 6
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java

@@ -184,7 +184,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
 
         // 2.1 生成退款单号
         // 一般来说,用户发起退款的时候,都会单独插入一个售后维权表,然后使用该表的 id 作为 refundId
-        // 这里我们是个简单的 demo,所以没有售后维权表,直接使用订单 id + "-refund" 来演示
+        //          这里我们是个简单的 demo,所以没有售后维权表,直接使用订单 id + "-refund" 来演示
         String refundId = order.getId() + "-refund";
         // 2.2 创建退款单
         Long payRefundId = payRefundApi.createRefund(new PayRefundCreateReqDTO()
@@ -215,16 +215,18 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
     }
 
     @Override
-    public void updateDemoOrderRefunded(Long id, Long payRefundId) {
+    public void updateDemoOrderRefunded(Long id, String refundId, Long payRefundId) {
         // 1. 校验并获得退款订单(可退款)
-        PayRefundRespDTO payRefund = validateDemoOrderCanRefunded(id, payRefundId);
+        PayRefundRespDTO payRefund = validateDemoOrderCanRefunded(id, refundId, payRefundId);
         // 2.2 更新退款单到 demo 订单
         payDemoOrderMapper.updateById(new PayDemoOrderDO().setId(id)
                 .setRefundTime(payRefund.getSuccessTime()));
     }
 
-    private PayRefundRespDTO validateDemoOrderCanRefunded(Long id, Long payRefundId) {
+    private PayRefundRespDTO validateDemoOrderCanRefunded(Long id, String refundId, Long payRefundId) {
         // 1.1 校验示例订单
+        // 一般来说,这里应该用 refundId 来查询退款单,然后再校验订单是否匹配
+        //       这里我们是个简单的 demo,所以没有售后维权表,直接使用订单 id 来查询订单
         PayDemoOrderDO order = payDemoOrderMapper.selectById(id);
         if (order == null) {
             throw exception(DEMO_ORDER_NOT_FOUND);
@@ -241,7 +243,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
         if (payRefund == null) {
             throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND);
         }
-        // 2.2
+        // 2.2 必须是退款成功状态
         if (!PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
             throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS);
         }
@@ -252,7 +254,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
             throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
         }
         // 2.4 校验退款订单匹配(二次)
-        if (notEqual(payRefund.getMerchantOrderId(), id.toString())) {
+        if (notEqual(payRefund.getMerchantRefundId(), id.toString() + "-refund")) {
             log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]",
                     id, payRefundId, toJsonString(payRefund));
             throw exception(DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);

+ 2 - 2
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java

@@ -75,7 +75,7 @@ public class PayDemoTransferServiceImpl implements PayDemoWithdrawService {
         // 2.1 创建支付单
         PayTransferCreateReqDTO transferReqDTO = new PayTransferCreateReqDTO()
                 .setAppKey(PAY_APP_KEY).setChannelCode(withdraw.getTransferChannelCode()).setUserIp(getClientIP()) // 支付应用
-                .setMerchantOrderId(String.valueOf(withdraw.getId())) // 业务的订单编号
+                .setMerchantTransferId(String.valueOf(withdraw.getId())) // 业务的订单编号
                 .setSubject(withdraw.getSubject()).setPrice(withdraw.getPrice()) // 价格信息
                 .setUserAccount(withdraw.getUserAccount()).setUserName(withdraw.getUserName()); // 收款信息
         if (ObjectUtil.equal(withdraw.getType(), PayDemoWithdrawTypeEnum.WECHAT.getType())) {
@@ -177,7 +177,7 @@ public class PayDemoTransferServiceImpl implements PayDemoWithdrawService {
             throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH);
         }
         // 2.3 校验转账订单匹配(二次)
-        if (ObjectUtil.notEqual(payTransfer.getMerchantOrderId(), withdraw.getId().toString())) {
+        if (ObjectUtil.notEqual(payTransfer.getMerchantTransferId(), withdraw.getId().toString())) {
             log.error("[validateDemoTransferStatusCanUpdate][withdraw({}) 转账单不匹配({}),请进行处理!payTransfer 数据是:{}]",
                     withdraw.getId(), payTransferId, JsonUtils.toJsonString(payTransfer));
             throw exception(DEMO_WITHDRAW_UPDATE_STATUS_FAIL_PAY_MERCHANT_EXISTS);

+ 26 - 11
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java

@@ -31,6 +31,7 @@ import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
 import com.google.common.annotations.VisibleForTesting;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.annotation.Lazy;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -97,19 +98,19 @@ public class PayNotifyServiceImpl implements PayNotifyService {
         PayNotifyTaskDO task = new PayNotifyTaskDO().setType(type).setDataId(dataId);
         task.setStatus(PayNotifyStatusEnum.WAITING.getStatus()).setNextNotifyTime(LocalDateTime.now())
                 .setNotifyTimes(0).setMaxNotifyTimes(PayNotifyTaskDO.NOTIFY_FREQUENCY.length + 1);
-        // 补充 appId + notifyUrl 字段
+        // 补充 appId + notifyUrl + merchant* 字段
         if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) {
             PayOrderDO order = orderService.getOrder(task.getDataId()); // 不进行非空判断,有问题直接异常
-            task.setAppId(order.getAppId()).
-                    setMerchantOrderId(order.getMerchantOrderId()).setNotifyUrl(order.getNotifyUrl());
+            task.setAppId(order.getAppId()).setNotifyUrl(order.getNotifyUrl())
+                    .setMerchantOrderId(order.getMerchantOrderId());
         } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) {
-            PayRefundDO refundDO = refundService.getRefund(task.getDataId());
-            task.setAppId(refundDO.getAppId())
-                    .setMerchantOrderId(refundDO.getMerchantOrderId()).setNotifyUrl(refundDO.getNotifyUrl());
+            PayRefundDO refund = refundService.getRefund(task.getDataId());
+            task.setAppId(refund.getAppId()).setNotifyUrl(refund.getNotifyUrl())
+                    .setMerchantOrderId(refund.getMerchantOrderId()).setMerchantRefundId(refund.getMerchantRefundId());
         } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.TRANSFER.getType())) {
             PayTransferDO transfer = transferService.getTransfer(task.getDataId());
-            task.setAppId(transfer.getAppId())
-                    .setMerchantOrderId(transfer.getMerchantOrderId()).setNotifyUrl(transfer.getNotifyUrl());
+            task.setAppId(transfer.getAppId()).setNotifyUrl(transfer.getNotifyUrl())
+                    .setMerchantTransferId(transfer.getMerchantTransferId());
         }
 
         // 执行插入
@@ -117,10 +118,13 @@ public class PayNotifyServiceImpl implements PayNotifyService {
 
         // 必须在事务提交后,在发起任务,否则 PayNotifyTaskDO 还没入库,就提前回调接入的业务
         TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
+
             @Override
             public void afterCommit() {
-                executeNotify(task);
+                // 异步的原因:避免阻塞当前事务,无需等待结果
+                getSelf().executeNotifyAsync(task);
             }
+
         });
     }
 
@@ -166,7 +170,17 @@ public class PayNotifyServiceImpl implements PayNotifyService {
     }
 
     /**
-     * 同步执行单个支付通知
+     * 异步执行单个支付通知
+     *
+     * @param task 通知任务
+     */
+    @Async
+    public void executeNotifyAsync(PayNotifyTaskDO task) {
+        executeNotify(task);
+    }
+
+    /**
+     * 【加锁】执行单个支付通知
      *
      * @param task 通知任务
      */
@@ -223,9 +237,10 @@ public class PayNotifyServiceImpl implements PayNotifyService {
                     .payOrderId(task.getDataId()).build();
         } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) {
             request = PayRefundNotifyReqDTO.builder().merchantOrderId(task.getMerchantOrderId())
+                    .merchantRefundId(task.getMerchantRefundId())
                     .payRefundId(task.getDataId()).build();
         } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.TRANSFER.getType())) {
-            request = PayTransferNotifyReqDTO.builder().merchantOrderId(task.getMerchantOrderId())
+            request = PayTransferNotifyReqDTO.builder().merchantTransferId(task.getMerchantTransferId())
                     .payTransferId(task.getDataId()).build();
         } else {
             throw new RuntimeException("未知的通知任务类型:" + JsonUtils.toJsonString(task));

+ 1 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java

@@ -62,7 +62,7 @@ public interface PayRefundService {
      * @param reqDTO 退款申请信息
      * @return 退款单号
      */
-    Long createPayRefund(PayRefundCreateReqDTO reqDTO);
+    Long createRefund(PayRefundCreateReqDTO reqDTO);
 
     /**
      * 渠道的退款通知

+ 1 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java

@@ -91,7 +91,7 @@ public class PayRefundServiceImpl implements PayRefundService {
     }
 
     @Override
-    public Long createPayRefund(PayRefundCreateReqDTO reqDTO) {
+    public Long createRefund(PayRefundCreateReqDTO reqDTO) {
         // 1.1 校验 App
         PayAppDO app = appService.validPayApp(reqDTO.getAppKey());
         // 1.2 校验支付订单

+ 1 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java

@@ -116,7 +116,7 @@ public class PayTransferServiceImpl implements PayTransferService {
     }
 
     private PayTransferDO validateTransferCanCreate(PayTransferCreateReqDTO reqDTO, Long appId) {
-        PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantOrderId(appId, reqDTO.getMerchantOrderId());
+        PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantOrderId(appId, reqDTO.getMerchantTransferId());
         if (transfer != null) {
             // 只有转账单状态为关闭,才能再次发起转账
             if (!PayTransferStatusEnum.isClosed(transfer.getStatus())) {

+ 6 - 6
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeService.java

@@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO;
-import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO;
 
 /**
  * 钱包充值 Service 接口
@@ -16,7 +15,7 @@ public interface PayWalletRechargeService {
     /**
      * 创建钱包充值记录(发起充值)
      *
-     * @param userId      用户 id
+     * @param userId      用户编号
      * @param userType    用户类型
      * @param createReqVO 钱包充值请求 VO
      * @param userIp  用户Ip
@@ -40,8 +39,8 @@ public interface PayWalletRechargeService {
     /**
      * 更新钱包充值成功
      *
-     * @param id         钱包充值记录 id
-     * @param payOrderId 支付订单 id
+     * @param id         钱包充值记录编号
+     * @param payOrderId 支付订单编号
      */
     void updateWalletRechargerPaid(Long id, Long payOrderId);
 
@@ -56,9 +55,10 @@ public interface PayWalletRechargeService {
     /**
      * 更新钱包充值记录为已退款
      *
-     * @param id          钱包充值 id
+     * @param id          钱包充值记录编号
+     * @param refundId    钱包充值退款编号(实际和 id 相同)
      * @param payRefundId 退款单id
      */
-    void updateWalletRechargeRefunded(Long id, Long payRefundId);
+    void updateWalletRechargeRefunded(Long id, Long refundId, Long payRefundId);
 
 }

+ 18 - 14
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletRechargeServiceImpl.java

@@ -8,10 +8,11 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
+import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
+import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
 import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.recharge.AppPayWalletRechargeCreateReqVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
-import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargeDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletRechargePackageDO;
@@ -21,7 +22,6 @@ import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
 import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
 import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
-import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
 import cn.iocoder.yudao.module.system.api.social.SocialClientApi;
 import cn.iocoder.yudao.module.system.api.social.dto.SocialWxaSubscribeMessageSendReqDTO;
 import jakarta.annotation.Resource;
@@ -61,13 +61,15 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
     private PayWalletService payWalletService;
     @Resource
     private PayOrderService payOrderService;
-    @Resource
-    private PayRefundService payRefundService;
+//    @Resource
+//    private PayRefundService payRefundService;
     @Resource
     private PayWalletRechargePackageService payWalletRechargePackageService;
 
     @Resource
     public SocialClientApi socialClientApi;
+    @Resource
+    private PayRefundApi payRefundApi;
 
     @Resource
     private PayProperties payProperties;
@@ -186,7 +188,7 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
         // 3. 创建退款单
         String walletRechargeId = String.valueOf(id);
         String refundId = walletRechargeId + "-refund";
-        Long payRefundId = payRefundService.createPayRefund(new PayRefundCreateReqDTO()
+        Long payRefundId = payRefundApi.createRefund(new PayRefundCreateReqDTO()
                 .setAppKey(payProperties.getWalletPayAppKey()).setUserIp(userIp)
                 .setMerchantOrderId(walletRechargeId)
                 .setMerchantRefundId(refundId)
@@ -199,18 +201,20 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void updateWalletRechargeRefunded(Long id, Long payRefundId) {
+    public void updateWalletRechargeRefunded(Long id, Long refundId, Long payRefundId) {
         // 1.1 获取钱包充值记录
+        // 说明:因为 id 和 refundId 是相同的,所以直接使用 id 查询即可!
         PayWalletRechargeDO walletRecharge = walletRechargeMapper.selectById(id);
         if (walletRecharge == null) {
             log.error("[updateWalletRechargerPaid][钱包充值记录不存在,钱包充值记录 id({})]", id);
             throw exception(WALLET_RECHARGE_NOT_FOUND);
         }
         // 1.2 校验钱包充值是否可以更新已退款
-        PayRefundDO payRefund = validateWalletRechargeCanRefunded(walletRecharge, payRefundId);
+        PayRefundRespDTO payRefund = validateWalletRechargeCanRefunded(walletRecharge, payRefundId);
 
+        // 2. 处理退款结果
         PayWalletRechargeDO updateObj = new PayWalletRechargeDO().setId(id);
-        // 退款成功
+        // 情况一:退款成功
         if (PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
             // 2.1 更新钱包余额
             payWalletService.reduceWalletBalance(walletRecharge.getWalletId(), id,
@@ -219,19 +223,19 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
             updateObj.setRefundStatus(SUCCESS.getStatus()).setRefundTime(payRefund.getSuccessTime())
                     .setRefundTotalPrice(walletRecharge.getTotalPrice()).setRefundPayPrice(walletRecharge.getPayPrice())
                     .setRefundBonusPrice(walletRecharge.getBonusPrice());
-        }
-        // 退款失败
-        if (PayRefundStatusRespEnum.isFailure(payRefund.getStatus())) {
+        // 情况二:退款失败
+        } else if (PayRefundStatusRespEnum.isFailure(payRefund.getStatus())) {
             // 2.2 解冻余额
             payWalletService.unfreezePrice(walletRecharge.getWalletId(), walletRecharge.getTotalPrice());
 
             updateObj.setRefundStatus(FAILURE.getStatus());
         }
+
         // 3. 更新钱包充值的退款字段
         walletRechargeMapper.updateByIdAndRefunded(id, WAITING.getStatus(), updateObj);
     }
 
-    private PayRefundDO validateWalletRechargeCanRefunded(PayWalletRechargeDO walletRecharge, Long payRefundId) {
+    private PayRefundRespDTO validateWalletRechargeCanRefunded(PayWalletRechargeDO walletRecharge, Long payRefundId) {
         // 1. 校验退款订单匹配
         if (notEqual(walletRecharge.getPayRefundId(), payRefundId)) {
             log.error("[validateWalletRechargeCanRefunded][钱包充值({}) 退款单不匹配({}),请进行处理!钱包充值的数据是:{}]",
@@ -240,7 +244,7 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
         }
 
         // 2.1 校验退款订单
-        PayRefundDO payRefund = payRefundService.getRefund(payRefundId);
+        PayRefundRespDTO payRefund = payRefundApi.getRefund(payRefundId);
         if (payRefund == null) {
             log.error("[validateWalletRechargeCanRefunded][payRefund({})不存在]", payRefundId);
             throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_NOT_FOUND);
@@ -252,7 +256,7 @@ public class PayWalletRechargeServiceImpl implements PayWalletRechargeService {
             throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
         }
         // 2.3 校验退款订单商户订单是否匹配
-        if (notEqual(payRefund.getMerchantOrderId(), walletRecharge.getId().toString())) {
+        if (notEqual(payRefund.getMerchantRefundId(), walletRecharge.getId().toString())) {
             log.error("[validateWalletRechargeCanRefunded][钱包({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]",
                     walletRecharge.getId(), payRefundId, toJsonString(payRefund));
             throw exception(WALLET_RECHARGE_REFUND_FAIL_REFUND_ORDER_ID_ERROR);

+ 8 - 8
yudao-module-pay/yudao-module-pay-biz/src/test/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceTest.java

@@ -215,7 +215,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
         when(appService.validPayApp(eq("demo"))).thenReturn(app);
 
         // 调用,并断言异常
-        assertServiceException(() -> refundService.createPayRefund(reqDTO),
+        assertServiceException(() -> refundService.createRefund(reqDTO),
                 PAY_ORDER_NOT_FOUND);
     }
 
@@ -241,7 +241,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
         when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
 
         // 调用,并断言异常
-        assertServiceException(() -> refundService.createPayRefund(reqDTO),
+        assertServiceException(() -> refundService.createRefund(reqDTO),
                 PAY_ORDER_REFUND_FAIL_STATUS_ERROR);
     }
 
@@ -260,7 +260,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
         when(orderService.getOrder(eq(1L), eq("100"))).thenReturn(order);
 
         // 调用,并断言异常
-        assertServiceException(() -> refundService.createPayRefund(reqDTO),
+        assertServiceException(() -> refundService.createRefund(reqDTO),
                 REFUND_PRICE_EXCEED);
     }
 
@@ -283,7 +283,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
         refundMapper.insert(refund);
 
         // 调用,并断言异常
-        assertServiceException(() -> refundService.createPayRefund(reqDTO),
+        assertServiceException(() -> refundService.createRefund(reqDTO),
                 REFUND_PRICE_EXCEED);
     }
 
@@ -307,7 +307,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
         when(channelService.validPayChannel(eq(1L))).thenReturn(channel);
 
         // 调用,并断言异常
-        assertServiceException(() -> refundService.createPayRefund(reqDTO),
+        assertServiceException(() -> refundService.createRefund(reqDTO),
                 CHANNEL_NOT_FOUND);
     }
 
@@ -339,7 +339,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
         refundMapper.insert(refund);
 
         // 调用,并断言异常
-        assertServiceException(() -> refundService.createPayRefund(reqDTO),
+        assertServiceException(() -> refundService.createRefund(reqDTO),
                 REFUND_EXISTS);
     }
 
@@ -369,7 +369,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
         when(client.unifiedRefund(any(PayRefundUnifiedReqDTO.class))).thenThrow(new RuntimeException());
 
         // 调用
-        Long refundId = refundService.createPayRefund(reqDTO);
+        Long refundId = refundService.createRefund(reqDTO);
         // 断言
         PayRefundDO refundDO = refundMapper.selectById(refundId);
         assertPojoEquals(reqDTO, refundDO);
@@ -422,7 +422,7 @@ public class PayRefundServiceTest extends BaseDbAndRedisUnitTest {
             }))).thenReturn(refundRespDTO);
 
             // 调用
-            Long refundId = refundService.createPayRefund(reqDTO);
+            Long refundId = refundService.createRefund(reqDTO);
             // 断言
             PayRefundDO refundDO = refundMapper.selectById(refundId);
             assertPojoEquals(reqDTO, refundDO);