Browse Source

【代码评审】商城:分销提现时,支持余额自动添加,以及微信提现到余额里

YunaiV 9 months ago
parent
commit
081ef0502c
28 changed files with 149 additions and 139 deletions
  1. 2 2
      yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawTypeEnum.java
  2. 8 6
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageWithdrawController.java
  3. 1 1
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/order/TradeOrderAutoCommentJob.java
  4. 11 6
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawService.java
  5. 57 54
      yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java
  6. 2 0
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayTransferNotifyReqDTO.java
  7. 4 2
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java
  8. 2 3
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java
  9. 6 1
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java
  10. 3 4
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java
  11. 2 3
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java
  12. 1 2
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppBaseVO.java
  13. 8 8
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java
  14. 1 3
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/transfer/PayTransferConvert.java
  15. 0 4
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java
  16. 5 10
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java
  17. 0 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java
  18. 1 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java
  19. 0 2
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java
  20. 1 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java
  21. 3 7
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java
  22. 1 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java
  23. 0 4
      yudao-module-pay/yudao-spring-boot-starter-biz-pay/pom.xml
  24. 1 0
      yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java
  25. 1 0
      yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java
  26. 1 0
      yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/WxPayTransferPartnerNotifyV3Result.java
  27. 1 0
      yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java
  28. 26 16
      yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java

+ 2 - 2
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawTypeEnum.java

@@ -17,9 +17,9 @@ public enum BrokerageWithdrawTypeEnum implements IntArrayValuable {
 
     WALLET(1, "钱包"),
     BANK(2, "银行卡"),
-    WECHAT(3, "微信"),
+    WECHAT(3, "微信"), // 手动打款
     ALIPAY(4, "支付宝"),
-    ALIPAY_SMALL(5, "微信零钱"),
+    WECHAT_API(5, "微信零钱"), // 自动打款,通过微信转账 API
     ;
 
     public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageWithdrawTypeEnum::getType).toArray();

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

@@ -46,7 +46,8 @@ public class BrokerageWithdrawController {
     @Operation(summary = "通过申请")
     @PreAuthorize("@ss.hasPermission('trade:brokerage-withdraw:audit')")
     public CommonResult<Boolean> approveBrokerageWithdraw(@RequestParam("id") Long id) {
-        brokerageWithdrawService.auditBrokerageWithdraw(id, BrokerageWithdrawStatusEnum.AUDIT_SUCCESS, "", getClientIP());
+        brokerageWithdrawService.auditBrokerageWithdraw(id,
+                BrokerageWithdrawStatusEnum.AUDIT_SUCCESS, "", getClientIP());
         return success(true);
     }
 
@@ -54,7 +55,8 @@ public class BrokerageWithdrawController {
     @Operation(summary = "驳回申请")
     @PreAuthorize("@ss.hasPermission('trade:brokerage-withdraw:audit')")
     public CommonResult<Boolean> rejectBrokerageWithdraw(@Valid @RequestBody BrokerageWithdrawRejectReqVO reqVO) {
-        brokerageWithdrawService.auditBrokerageWithdraw(reqVO.getId(), BrokerageWithdrawStatusEnum.AUDIT_FAIL, reqVO.getAuditReason(), getClientIP());
+        brokerageWithdrawService.auditBrokerageWithdraw(reqVO.getId(),
+                BrokerageWithdrawStatusEnum.AUDIT_FAIL, reqVO.getAuditReason(), getClientIP());
         return success(true);
     }
 
@@ -80,14 +82,14 @@ public class BrokerageWithdrawController {
         return success(BrokerageWithdrawConvert.INSTANCE.convertPage(pageResult, userMap));
     }
 
+    // TODO @luchi:update-transferred,url 改成这个。和 update-paid 、update-refunded 保持一致
     @PostMapping("/update-transfer")
     @Operation(summary = "更新转账订单为转账成功") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
     @PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现
-    public CommonResult<Boolean> updateAfterRefund(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) {
-        // 目前业务逻辑,不需要做任何事情
-        // 当然,退款会有小概率会失败的情况,可以监控失败状态,进行告警
+    public CommonResult<Boolean> updateBrokerageWithdrawTransferred(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) {
         log.info("[updateAfterRefund][notifyReqDTO({})]", notifyReqDTO);
-        brokerageWithdrawService.updateTransfer(Long.parseLong(notifyReqDTO.getMerchantTransferId()), notifyReqDTO.getPayTransferId());
+        brokerageWithdrawService.updateBrokerageWithdrawTransferred(
+                Long.parseLong(notifyReqDTO.getMerchantTransferId()), notifyReqDTO.getPayTransferId());
         return success(true);
     }
 

+ 1 - 1
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/job/order/TradeOrderAutoCommentJob.java

@@ -21,7 +21,7 @@ public class TradeOrderAutoCommentJob implements JobHandler {
     @Override
     @TenantJob
     public String execute(String param) {
-            int count = tradeOrderUpdateService.createOrderItemCommentBySystem();
+        int count = tradeOrderUpdateService.createOrderItemCommentBySystem();
         return String.format("评论订单 %s 个", count);
     }
 

+ 11 - 6
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawService.java

@@ -27,6 +27,7 @@ public interface BrokerageWithdrawService {
      * @param id          佣金编号
      * @param status      审核状态
      * @param auditReason 驳回原因
+     * @param userIp 操作 IP
      */
     void auditBrokerageWithdraw(Long id, BrokerageWithdrawStatusEnum status, String auditReason, String userIp);
 
@@ -55,6 +56,16 @@ public interface BrokerageWithdrawService {
      */
     Long createBrokerageWithdraw(Long userId, AppBrokerageWithdrawCreateReqVO createReqVO);
 
+    /**
+     * 【API】更新佣金提现的转账结果
+     *
+     * 目前用于支付回调,标记提现转账结果
+     *
+     * @param id 提现编号
+     * @param payTransferId 转账订单编号
+     */
+    void updateBrokerageWithdrawTransferred(Long id, Long payTransferId);
+
     /**
      * 按照 userId,汇总每个用户的提现
      *
@@ -77,10 +88,4 @@ public interface BrokerageWithdrawService {
         return convertMap(getWithdrawSummaryListByUserId(userIds, status), BrokerageWithdrawSummaryRespBO::getUserId);
     }
 
-    /**
-     *
-     * @param merchantTransferId 提现编号
-     * @param payTransferId 转账订单编号
-     */
-    void updateTransfer(Long merchantTransferId, Long payTransferId);
 }

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

@@ -1,8 +1,6 @@
 package cn.iocoder.yudao.module.trade.service.brokerage;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.date.LocalDateTimeUtil;
-import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
@@ -17,7 +15,6 @@ import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
 import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum;
 import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
 import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
-import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
 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;
@@ -27,7 +24,6 @@ import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageWithdrawConvert;
 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO;
 import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
 import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageWithdrawMapper;
-import cn.iocoder.yudao.module.trade.dal.redis.no.TradeNoRedisDAO;
 import cn.iocoder.yudao.module.trade.enums.MessageTemplateConstants;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
@@ -35,14 +31,16 @@ import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
 import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
 import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageWithdrawSummaryRespBO;
 import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
+import jakarta.annotation.Resource;
+import jakarta.validation.Validator;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
-import jakarta.annotation.Resource;
-import jakarta.validation.Validator;
 import java.time.LocalDateTime;
-import java.util.*;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
@@ -58,8 +56,6 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
 
     @Resource
     private BrokerageWithdrawMapper brokerageWithdrawMapper;
-    @Resource
-    private TradeNoRedisDAO tradeNoRedisDAO;
 
     @Resource
     private BrokerageRecordService brokerageRecordService;
@@ -104,6 +100,7 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
             templateCode = MessageTemplateConstants.SMS_BROKERAGE_WITHDRAW_AUDIT_APPROVE;
             // 3.1 通过时佣金转余额
             if (BrokerageWithdrawTypeEnum.WALLET.getType().equals(withdraw.getType())) {
+                // TODO 改成直接调用 addWallet 增加余额,不用查询 wallet
                 PayWalletRespDTO wallet = payWalletApi.getWalletByUserId(withdraw.getUserId());
                 payWalletApi.addWallet(new PayWalletCreateReqDto()
                         .setWalletId(wallet.getId())
@@ -116,12 +113,9 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
                 if (rows == 0) {
                     throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
                 }
-            }else if (BrokerageWithdrawTypeEnum.ALIPAY_SMALL.getType().equals(withdraw.getType())){
-                //获取openid
-                SocialUserRespDTO socialUser = socialUserApi.getSocialUserByUserId(UserTypeEnum.MEMBER.getValue(), withdraw.getUserId(), SocialTypeEnum.WECHAT_MINI_APP.getType());
-                // 微信提现
-                PayTransferCreateReqDTO payTransferCreateReqDTO = getPayTransferCreateReqDTO(userIp, withdraw, socialUser);
-                payTransferApi.createTransfer(payTransferCreateReqDTO);
+            } else if (BrokerageWithdrawTypeEnum.WECHAT_API.getType().equals(withdraw.getType())) {
+                // TODO @luchi:这里,要加个转账单号的记录;另外,调用 API 转账,是立马成功,还是有延迟的哈?
+                Long payTransferId = createPayTransfer(userIp, withdraw);
             }
         } else if (BrokerageWithdrawStatusEnum.AUDIT_FAIL.equals(status)) {
             templateCode = MessageTemplateConstants.SMS_BROKERAGE_WITHDRAW_AUDIT_REJECT;
@@ -132,6 +126,7 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
             throw new IllegalArgumentException("不支持的提现状态:" + status);
         }
 
+        // TODO @luchi:这个通知,还是要的呀~~~
         // 4. 通知用户
 //        Map<String, Object> templateParams = MapUtil.<String, Object>builder()
 //                .put("createTime", LocalDateTimeUtil.formatNormal(withdraw.getCreateTime()))
@@ -142,17 +137,21 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
 //                .setUserId(withdraw.getUserId()).setTemplateCode(templateCode).setTemplateParams(templateParams));
     }
 
-    private PayTransferCreateReqDTO getPayTransferCreateReqDTO(String userIp, BrokerageWithdrawDO withdraw, SocialUserRespDTO socialUser) {
-        PayTransferCreateReqDTO payTransferCreateReqDTO = new PayTransferCreateReqDTO();
-        payTransferCreateReqDTO.setAppKey(tradeOrderProperties.getPayAppKey());
-        payTransferCreateReqDTO.setChannelCode("wx_lite");
-        payTransferCreateReqDTO.setUserIp(userIp);
-        payTransferCreateReqDTO.setType(PayTransferTypeEnum.WX_BALANCE.getType());
-        payTransferCreateReqDTO.setMerchantTransferId(withdraw.getId().toString());
-        payTransferCreateReqDTO.setPrice(withdraw.getPrice());
-        payTransferCreateReqDTO.setSubject("佣金提现");
-        payTransferCreateReqDTO.setOpenid(socialUser.getOpenid());
-        return payTransferCreateReqDTO;
+    private Long createPayTransfer(String userIp, BrokerageWithdrawDO withdraw) {
+        // 1.1 获取微信 openid
+        SocialUserRespDTO socialUser = socialUserApi.getSocialUserByUserId(
+                UserTypeEnum.MEMBER.getValue(), withdraw.getUserId(), SocialTypeEnum.WECHAT_MINI_APP.getType());
+        // TODO @luchi:这里,需要校验非空。如果空的话,要有业务异常哈;
+        // 1.2 构建请求
+        PayTransferCreateReqDTO payTransferCreateReqDTO = new PayTransferCreateReqDTO()
+                .setAppKey(tradeOrderProperties.getPayAppKey())
+                .setChannelCode("wx_lite").setType(PayTransferTypeEnum.WX_BALANCE.getType())
+                .setMerchantTransferId(withdraw.getId().toString())
+                .setPrice(withdraw.getPrice())
+                .setSubject("佣金提现")
+                .setOpenid(socialUser.getOpenid()).setUserIp(userIp);
+        // 2. 发起请求
+        return payTransferApi.createTransfer(payTransferCreateReqDTO);
     }
 
     private BrokerageWithdrawDO validateBrokerageWithdrawExists(Long id) {
@@ -194,32 +193,6 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
         return withdraw.getId();
     }
 
-    @Override
-    public List<BrokerageWithdrawSummaryRespBO> getWithdrawSummaryListByUserId(Collection<Long> userIds,
-                                                                               BrokerageWithdrawStatusEnum status) {
-        if (CollUtil.isEmpty(userIds)) {
-            return Collections.emptyList();
-        }
-        return brokerageWithdrawMapper.selectCountAndSumPriceByUserIdAndStatus(userIds, status.getStatus());
-    }
-
-    @Override
-    public void updateTransfer(Long id, Long payTransferId) {
-        BrokerageWithdrawDO withdraw = validateBrokerageWithdrawExists(id);
-        PayTransferRespDTO transfer = payTransferApi.getTransfer(payTransferId);
-        if(PayTransferStatusEnum.isSuccess(transfer.getStatus())){
-            withdraw.setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus());
-        }else if(PayTransferStatusEnum.isPendingStatus(transfer.getStatus())){
-            withdraw.setStatus(BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus());
-        }else{
-            withdraw.setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_FAIL.getStatus());
-            // 3.2 驳回时需要退还用户佣金
-            brokerageRecordService.addBrokerage(withdraw.getUserId(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT,
-                    String.valueOf(withdraw.getId()), withdraw.getPrice(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT.getTitle());
-        }
-        brokerageWithdrawMapper.updateById(withdraw);
-    }
-
     /**
      * 计算提现手续费
      *
@@ -227,7 +200,7 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
      * @param percent       手续费百分比
      * @return 提现手续费
      */
-    Integer calculateFeePrice(Integer withdrawPrice, Integer percent) {
+    private Integer calculateFeePrice(Integer withdrawPrice, Integer percent) {
         Integer feePrice = 0;
         if (percent != null && percent > 0) {
             feePrice = MoneyUtils.calculateRatePrice(withdrawPrice, Double.valueOf(percent));
@@ -241,11 +214,41 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
      * @param withdrawPrice 提现金额
      * @return 分销配置
      */
-    TradeConfigDO validateWithdrawPrice(Integer withdrawPrice) {
+    private TradeConfigDO validateWithdrawPrice(Integer withdrawPrice) {
         TradeConfigDO tradeConfig = tradeConfigService.getTradeConfig();
         if (tradeConfig.getBrokerageWithdrawMinPrice() != null && withdrawPrice < tradeConfig.getBrokerageWithdrawMinPrice()) {
             throw exception(BROKERAGE_WITHDRAW_MIN_PRICE, MoneyUtils.fenToYuanStr(tradeConfig.getBrokerageWithdrawMinPrice()));
         }
         return tradeConfig;
     }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateBrokerageWithdrawTransferred(Long id, Long payTransferId) {
+        BrokerageWithdrawDO withdraw = validateBrokerageWithdrawExists(id);
+        PayTransferRespDTO transfer = payTransferApi.getTransfer(payTransferId);
+        // TODO @luchi:建议参考支付那,即使成功的情况下,也要各种校验;金额是否匹配、转账单号是否匹配、是否重复调用;
+        if (PayTransferStatusEnum.isSuccess(transfer.getStatus())) {
+            withdraw.setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus());
+        } else if (PayTransferStatusEnum.isPendingStatus(transfer.getStatus())) {
+            // TODO @luchi:这里,是不是不用更新哈?
+            withdraw.setStatus(BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus());
+        } else {
+            withdraw.setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_FAIL.getStatus());
+            // 3.2 驳回时需要退还用户佣金
+            brokerageRecordService.addBrokerage(withdraw.getUserId(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT,
+                    String.valueOf(withdraw.getId()), withdraw.getPrice(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT.getTitle());
+        }
+        brokerageWithdrawMapper.updateById(withdraw);
+    }
+
+    @Override
+    public List<BrokerageWithdrawSummaryRespBO> getWithdrawSummaryListByUserId(Collection<Long> userIds,
+                                                                               BrokerageWithdrawStatusEnum status) {
+        if (CollUtil.isEmpty(userIds)) {
+            return Collections.emptyList();
+        }
+        return brokerageWithdrawMapper.selectCountAndSumPriceByUserIdAndStatus(userIds, status.getStatus());
+    }
+
 }

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

@@ -13,6 +13,7 @@ import jakarta.validation.constraints.NotNull;
 @Data
 public class PayTransferNotifyReqDTO {
 
+    // TODO 芋艿:要不要改成 orderId 待定;
     /**
      * 商户转账单号
      */
@@ -24,4 +25,5 @@ public class PayTransferNotifyReqDTO {
      */
     @NotNull(message = "转账订单编号不能为空")
     private Long payTransferId;
+
 }

+ 4 - 2
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java

@@ -21,9 +21,11 @@ public interface PayTransferApi {
     Long createTransfer(@Valid PayTransferCreateReqDTO reqDTO);
 
     /**
-     * 获取转账单详细
+     * 获得转账单
+     *
      * @param id 转账单编号
-     * @return 转账单详细
+     * @return 转账单
      */
     PayTransferRespDTO getTransfer(Long id);
+
 }

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

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.pay.api.transfer.dto;
 
+import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
 import lombok.Data;
 
 @Data
@@ -12,7 +13,6 @@ public class PayTransferRespDTO {
 
     /**
      * 转账单号
-     *
      */
     private String no;
 
@@ -24,9 +24,8 @@ public class PayTransferRespDTO {
     /**
      * 转账状态
      *
-     * 枚举 {@link PayTransferStatusRespEnum}
+     * 枚举 {@link PayTransferStatusEnum}
      */
     private Integer status;
 
-
 }

+ 6 - 1
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java

@@ -10,16 +10,21 @@ import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
  */
 public interface PayWalletApi {
 
+    // TODO @luchi:1)改成 addWalletBalance;2)PayWalletCreateReqDto 搞成 userId、userType;3)bizType 使用 integer,不然后续挪到 cloud 不好弄,因为枚举不好序列化
     /**
      * 添加钱包
+     *
      * @param reqDTO 创建请求
      */
     void addWallet(PayWalletCreateReqDto reqDTO);
 
+    // TODO @luchi:不用去 getWalletByUserId 钱包,直接添加余额就好。里面内部去创建。如果删除掉的化,PayWalletRespDTO 也删除哈。
     /**
-     * 根据用户id获取钱包信息
+     * 根据用户编号,获取钱包信息
+     *
      * @param userId 用户id
      * @return 钱包信息
      */
     PayWalletRespDTO getWalletByUserId(Long userId);
+
 }

+ 3 - 4
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java

@@ -1,15 +1,14 @@
 package cn.iocoder.yudao.module.pay.api.transfer;
 
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
-import cn.iocoder.yudao.module.pay.convert.transfer.PayTransferConvert;
 import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
 import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
+import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
-import jakarta.annotation.Resource;
-
 /**
  * 转账单 API 实现类
  *
@@ -30,7 +29,7 @@ public class PayTransferApiImpl implements PayTransferApi {
     @Override
     public PayTransferRespDTO getTransfer(Long id) {
         PayTransferDO transfer = payTransferService.getTransfer(id);
-        return PayTransferConvert.INSTANCE.convert3(transfer);
+        return BeanUtils.toBean(transfer, PayTransferRespDTO.class);
     }
 
 }

+ 2 - 3
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java

@@ -1,9 +1,9 @@
 package cn.iocoder.yudao.module.pay.api.wallet;
 
 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletCreateReqDto;
 import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
-import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletConvert;
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
 import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
 import jakarta.annotation.Resource;
@@ -22,13 +22,12 @@ public class PayWalletApiImpl implements PayWalletApi {
 
     @Override
     public void addWallet(PayWalletCreateReqDto reqDTO) {
-        //添加钱包金额
         payWalletService.addWalletBalance(reqDTO.getWalletId(), reqDTO.getBizId(), reqDTO.getBizType(), reqDTO.getPrice());
     }
 
     @Override
     public PayWalletRespDTO getWalletByUserId(Long userId) {
         PayWalletDO orCreateWallet = payWalletService.getOrCreateWallet(userId, UserTypeEnum.MEMBER.getValue());
-        return PayWalletConvert.INSTANCE.convert03(orCreateWallet);
+        return BeanUtils.toBean(orCreateWallet, PayWalletRespDTO.class);
     }
 }

+ 1 - 2
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppBaseVO.java

@@ -40,8 +40,7 @@ public class PayAppBaseVO {
     @URL(message = "退款结果的回调地址必须为 URL 格式")
     private String refundNotifyUrl;
 
-    @Schema(description = "转账结果的回调地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://127.0.0.1:48080/refund-callback")
-    @NotNull(message = "转账结果的回调地址不能为空")
+    @Schema(description = "转账结果的回调地址", example = "http://127.0.0.1:48080/transfer-callback")
     @URL(message = "转账结果的回调地址必须为 URL 格式")
     private String transferNotifyUrl;
 

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

@@ -69,7 +69,7 @@ public class PayNotifyController {
         // 1. 校验支付渠道是否存在
         PayClient payClient = channelService.getPayClient(channelId);
         if (payClient == null) {
-            log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
+            log.error("[notifyOrder][渠道编号({}) 找不到对应的支付客户端]", channelId);
             throw exception(CHANNEL_NOT_FOUND);
         }
 
@@ -83,13 +83,13 @@ public class PayNotifyController {
     @Operation(summary = "支付渠道的统一【退款】回调")
     @PermitAll
     public String notifyRefund(@PathVariable("channelId") Long channelId,
-                              @RequestParam(required = false) Map<String, String> params,
-                              @RequestBody(required = false) String body) {
+                               @RequestParam(required = false) Map<String, String> params,
+                               @RequestBody(required = false) String body) {
         log.info("[notifyRefund][channelId({}) 回调数据({}/{})]", channelId, params, body);
         // 1. 校验支付渠道是否存在
         PayClient payClient = channelService.getPayClient(channelId);
         if (payClient == null) {
-            log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
+            log.error("[notifyRefund][渠道编号({}) 找不到对应的支付客户端]", channelId);
             throw exception(CHANNEL_NOT_FOUND);
         }
 
@@ -103,13 +103,13 @@ public class PayNotifyController {
     @Operation(summary = "支付渠道的统一【转账】回调")
     @PermitAll
     public String notifyTransfer(@PathVariable("channelId") Long channelId,
-                               @RequestParam(required = false) Map<String, String> params,
-                               @RequestBody(required = false) String body) {
-        log.info("[notifyRefund][channelId({}) 回调数据({}/{})]", channelId, params, body);
+                                 @RequestParam(required = false) Map<String, String> params,
+                                 @RequestBody(required = false) String body) {
+        log.info("[notifyTransfer][channelId({}) 回调数据({}/{})]", channelId, params, body);
         // 1. 校验支付渠道是否存在
         PayClient payClient = channelService.getPayClient(channelId);
         if (payClient == null) {
-            log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
+            log.error("[notifyTransfer][渠道编号({}) 找不到对应的支付客户端]", channelId);
             throw exception(CHANNEL_NOT_FOUND);
         }
 

+ 1 - 3
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/transfer/PayTransferConvert.java

@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.pay.convert.transfer;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
-import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
 import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferCreateReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferCreateReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageItemRespVO;
@@ -27,7 +26,6 @@ public interface PayTransferConvert {
 
     PayTransferRespVO convert(PayTransferDO bean);
 
-    PayTransferRespDTO convert3(PayTransferDO bean);
-
     PageResult<PayTransferPageItemRespVO> convertPage(PageResult<PayTransferDO> pageResult);
+
 }

+ 0 - 4
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java

@@ -1,8 +1,6 @@
 package cn.iocoder.yudao.module.pay.convert.wallet;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletCreateReqDto;
-import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
 import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletRespVO;
 import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet.AppPayWalletRespVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
@@ -20,6 +18,4 @@ public interface PayWalletConvert {
 
     PageResult<PayWalletRespVO> convertPage(PageResult<PayWalletDO> page);
 
-    PayWalletRespDTO convert03(PayWalletDO orCreateWallet);
-
 }

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

@@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO;
-import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import org.apache.ibatis.annotations.Mapper;
@@ -24,10 +23,6 @@ public interface PayTransferMapper extends BaseMapperX<PayTransferDO> {
                     PayTransferDO::getMerchantTransferId, merchantTransferId);
     }
 
-    default PayTransferDO selectByNo(String no){
-        return selectOne(PayTransferDO::getNo, no);
-    }
-
     default PageResult<PayTransferDO> selectPage(PayTransferPageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<PayTransferDO>()
                 .eqIfPresent(PayTransferDO::getNo, reqVO.getNo())
@@ -42,15 +37,15 @@ public interface PayTransferMapper extends BaseMapperX<PayTransferDO> {
                 .orderByDesc(PayTransferDO::getId));
     }
 
-    default List<PayTransferDO> selectListByStatus(Integer status){
+    default List<PayTransferDO> selectListByStatus(Integer status) {
         return selectList(PayTransferDO::getStatus, status);
     }
 
-    default PayTransferDO selectByAppIdAndNo(Long appId, String no){
-        return selectOne(new LambdaQueryWrapperX<PayTransferDO>()
-                .eq(PayTransferDO::getAppId, appId)
-                .eq(PayTransferDO::getNo, no));
+    default PayTransferDO selectByAppIdAndNo(Long appId, String no) {
+        return selectOne(PayTransferDO::getAppId, appId,
+                PayTransferDO::getNo, no);
     }
+
 }
 
 

+ 0 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/wallet/PayWalletMapper.java

@@ -114,7 +114,6 @@ public interface PayWalletMapper extends BaseMapperX<PayWalletDO> {
         return update(null, lambdaUpdateWrapper);
     }
 
-
 }
 
 

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

@@ -78,4 +78,5 @@ public interface PayRefundService {
      * @return 同步到状态的退款数量,包括退款成功、退款失败
      */
     int syncRefund();
+
 }

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

@@ -193,8 +193,6 @@ public class PayRefundServiceImpl implements PayRefundService {
         TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyRefund(channel, notify));
     }
 
-
-
     /**
      * 通知并更新订单的退款结果
      *

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

@@ -63,4 +63,5 @@ public interface PayTransferService {
      * @param notify     通知
      */
     void notifyTransfer(Long channelId, PayTransferRespDTO notify);
+
 }

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

@@ -29,7 +29,6 @@ import jakarta.annotation.Resource;
 import jakarta.validation.Validator;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
@@ -171,8 +170,7 @@ public class PayTransferServiceImpl implements PayTransferService {
 
     private void notifyTransferInProgress(PayChannelDO channel, PayTransferRespDTO notify) {
         // 1.校验
-        PayTransferDO transfer = transferMapper.selectByAppIdAndNo(
-                channel.getAppId(), notify.getOutTransferNo());
+        PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo());
         if (transfer == null) {
             throw exception(PAY_TRANSFER_NOT_FOUND);
         }
@@ -195,8 +193,7 @@ public class PayTransferServiceImpl implements PayTransferService {
 
     private void notifyTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) {
         // 1.校验
-        PayTransferDO transfer = transferMapper.selectByAppIdAndNo(
-                channel.getAppId(), notify.getOutTransferNo());
+        PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo());
         if (transfer == null) {
             throw exception(PAY_TRANSFER_NOT_FOUND);
         }
@@ -225,8 +222,7 @@ public class PayTransferServiceImpl implements PayTransferService {
 
     private void notifyTransferClosed(PayChannelDO channel, PayTransferRespDTO notify) {
         // 1.校验
-        PayTransferDO transfer = transferMapper.selectByAppIdAndNo(
-                channel.getAppId(), notify.getOutTransferNo());
+        PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo());
         if (transfer == null) {
             throw exception(PAY_TRANSFER_NOT_FOUND);
         }

+ 1 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java

@@ -200,6 +200,7 @@ public class PayWalletServiceImpl implements PayWalletService {
                 case UPDATE_BALANCE: // 更新余额
                     walletMapper.updateWhenRecharge(payWallet.getId(), price);
                     break;
+                // TODO @luchi:1)不能使用 updateWhenRecharge,它是充值哈。应该增加余额就 ok 了。。ps:顺便帮我把 UPDATE_BALANCE 也改改哈;2)其实不用 WITHDRAW 枚举,复用 UPDATE_BALANCE 就好了。
                 case WITHDRAW:
                     walletMapper.updateWhenRecharge(payWallet.getId(), price);
                     break;

+ 0 - 4
yudao-module-pay/yudao-spring-boot-starter-biz-pay/pom.xml

@@ -64,10 +64,6 @@
             <groupId>com.github.binarywang</groupId>
             <artifactId>weixin-java-pay</artifactId>
         </dependency>
-        <dependency>
-            <groupId>com.github.binarywang</groupId>
-            <artifactId>wx-java-miniapp-spring-boot-starter</artifactId>
-        </dependency>
 
         <!-- Test 测试相关 -->
         <dependency>

+ 1 - 0
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java

@@ -106,4 +106,5 @@ public interface PayClient {
      * @return 转账信息
      */
     PayTransferRespDTO parseTransferNotify(Map<String, String> params, String body);
+
 }

+ 1 - 0
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java

@@ -84,4 +84,5 @@ public class PayTransferUnifiedReqDTO {
     @NotEmpty(message = "转账结果的回调地址不能为空")
     @URL(message = "转账结果的 notify 回调地址必须是 URL 格式")
     private String notifyUrl;
+
 }

+ 1 - 0
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/WxPayTransferPartnerNotifyV3Result.java

@@ -8,6 +8,7 @@ import lombok.NoArgsConstructor;
 
 import java.io.Serializable;
 
+// TODO @luchi:这个可以复用 wxjava 里的类么?
 @NoArgsConstructor
 public class WxPayTransferPartnerNotifyV3Result implements Serializable, WxPayBaseNotifyV3Result<WxPayTransferPartnerNotifyV3Result.TransferNotifyResult> {
 

+ 1 - 0
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayPayClient.java

@@ -325,6 +325,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
         }
     }
 
+    // TODO @chihuo:这里是不是也要实现,支付宝的。
     @Override
     protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body) throws Throwable {
         throw new UnsupportedOperationException("未实现");

+ 26 - 16
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java

@@ -18,21 +18,23 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.WxPayTransferPart
 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
 import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
 import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
-import com.github.binarywang.wxpay.bean.notify.*;
+import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result;
+import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
+import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
+import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
 import com.github.binarywang.wxpay.bean.request.*;
 import com.github.binarywang.wxpay.bean.result.*;
 import com.github.binarywang.wxpay.bean.transfer.TransferBatchesRequest;
 import com.github.binarywang.wxpay.bean.transfer.TransferBatchesResult;
 import com.github.binarywang.wxpay.config.WxPayConfig;
 import com.github.binarywang.wxpay.exception.WxPayException;
-import com.github.binarywang.wxpay.service.TransferService;
 import com.github.binarywang.wxpay.service.WxPayService;
 import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
 import lombok.extern.slf4j.Slf4j;
 
 import java.time.LocalDateTime;
 import java.time.ZoneId;
-import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -356,17 +358,21 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
         switch (config.getApiVersion()) {
             case API_VERSION_V3:
                 return parseTransferNotifyV3(body);
+            case API_VERSION_V2:
+                throw new UnsupportedOperationException("V2 版本暂不支持,建议使用 V3 版本");
             default:
                 throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
         }
     }
 
     private PayTransferRespDTO parseTransferNotifyV3(String body) throws WxPayException {
+        // 1. 解析回调
+        // TODO @luchi:这个可以复用 wxjava 里的类么?
         WxPayTransferPartnerNotifyV3Result response = client.baseParseOrderNotifyV3Result(body, null, WxPayTransferPartnerNotifyV3Result.class, WxPayTransferPartnerNotifyV3Result.TransferNotifyResult.class);
         WxPayTransferPartnerNotifyV3Result.TransferNotifyResult result = response.getResult();
         // 2. 构建结果
         if (Objects.equals("FINISHED", result.getBatchStatus())) {
-            if(result.getFailNum() <= 0){
+            if (result.getFailNum() <= 0) {
                 return PayTransferRespDTO.successOf(result.getBatchId(), parseDateV3(result.getUpdateTime()),
                         result.getOutBatchNo(), response);
             }
@@ -453,29 +459,33 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
 
     @Override
     protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws WxPayException {
-        TransferService transferService = client.getTransferService();
-        List<TransferBatchesRequest.TransferDetail> transferDetailList = new ArrayList<>();
-        transferDetailList.add(TransferBatchesRequest.TransferDetail.newBuilder()
-                .outDetailNo(reqDTO.getOutTransferNo())
-                .transferAmount(reqDTO.getPrice())
-                .transferRemark(reqDTO.getSubject())
-                .openid(reqDTO.getOpenid())
-                .build());
+        // 1. 构建 TransferBatchesRequest 请求
+        List<TransferBatchesRequest.TransferDetail> transferDetailList = Collections.singletonList(
+                TransferBatchesRequest.TransferDetail.newBuilder()
+                        .outDetailNo(reqDTO.getOutTransferNo())
+                        .transferAmount(reqDTO.getPrice())
+                        .transferRemark(reqDTO.getSubject())
+                        .openid(reqDTO.getOpenid())
+                        .build());
+        // TODO @luchi:能不能我们搞个 TransferBatchesRequestX extends TransferBatchesRequest,这样更简洁一点。
         TransferBatchesRequest transferBatches = TransferBatchesRequest.newBuilder()
                 .appid(this.config.getAppId())
                 .outBatchNo(reqDTO.getOutTransferNo())
                 .batchName(reqDTO.getSubject())
                 .batchRemark(reqDTO.getSubject())
                 .totalAmount(reqDTO.getPrice())
-                .totalNum(1)
-                .transferDetailList(transferDetailList).build();
-        transferBatches.setNotifyUrl(reqDTO.getNotifyUrl());
-        TransferBatchesResult transferBatchesResult = transferService.transferBatches(transferBatches);
+                .totalNum(transferDetailList.size())
+                .transferDetailList(transferDetailList).build()
+                .setNotifyUrl(reqDTO.getNotifyUrl());
+        // 2.1 执行请求
+        TransferBatchesResult transferBatchesResult = client.getTransferService().transferBatches(transferBatches);
+        // 2.2 创建返回结果
         return PayTransferRespDTO.dealingOf(transferBatchesResult.getBatchId(), reqDTO.getOutTransferNo(), transferBatchesResult);
     }
 
     @Override
     protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) {
+        // TODO luchi:这个最好实现下。因为可能要主动轮询转账结果
         throw new UnsupportedOperationException("待实现");
     }