Commit 602bd5f93c4d5fa50d6669c0c5d0daf624090b55

Authored by 芋道源码
Committed by Gitee
2 parents fd5af249 1a36c5c8

!1099 【新增功能】添加微信转账到零钱提现功能

Merge pull request !1099 from 痴货/jh-wxzz
Showing 46 changed files with 746 additions and 49 deletions
yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawTypeEnum.java
... ... @@ -19,6 +19,7 @@ public enum BrokerageWithdrawTypeEnum implements IntArrayValuable {
19 19 BANK(2, "银行卡"),
20 20 WECHAT(3, "微信"),
21 21 ALIPAY(4, "支付宝"),
  22 + ALIPAY_SMALL(5, "微信零钱"),
22 23 ;
23 24  
24 25 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageWithdrawTypeEnum::getType).toArray();
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageRecordController.java
... ... @@ -44,7 +44,7 @@ public class BrokerageRecordController {
44 44 @Operation(summary = "获得佣金记录")
45 45 @Parameter(name = "id", description = "编号", required = true, example = "1024")
46 46 @PreAuthorize("@ss.hasPermission('trade:brokerage-record:query')")
47   - public CommonResult<BrokerageRecordRespVO> getBrokerageRecord(@RequestParam("id") Integer id) {
  47 + public CommonResult<BrokerageRecordRespVO> getBrokerageRecord(@RequestParam("id") Long id) {
48 48 BrokerageRecordDO brokerageRecord = brokerageRecordService.getBrokerageRecord(id);
49 49 return success(BrokerageRecordConvert.INSTANCE.convert(brokerageRecord));
50 50 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageWithdrawController.java
... ... @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5 import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
6 6 import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
  7 +import cn.iocoder.yudao.module.pay.api.notify.dto.PayTransferNotifyReqDTO;
7 8 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawRejectReqVO;
8 9 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawPageReqVO;
9 10 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawRespVO;
... ... @@ -14,6 +15,8 @@ import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageWithdrawService;
14 15 import io.swagger.v3.oas.annotations.Operation;
15 16 import io.swagger.v3.oas.annotations.Parameter;
16 17 import io.swagger.v3.oas.annotations.tags.Tag;
  18 +import jakarta.annotation.security.PermitAll;
  19 +import lombok.extern.slf4j.Slf4j;
17 20 import org.springframework.security.access.prepost.PreAuthorize;
18 21 import org.springframework.validation.annotation.Validated;
19 22 import org.springframework.web.bind.annotation.*;
... ... @@ -24,11 +27,13 @@ import java.util.Map;
24 27  
25 28 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
26 29 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  30 +import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
27 31  
28 32 @Tag(name = "管理后台 - 佣金提现")
29 33 @RestController
30 34 @RequestMapping("/trade/brokerage-withdraw")
31 35 @Validated
  36 +@Slf4j
32 37 public class BrokerageWithdrawController {
33 38  
34 39 @Resource
... ... @@ -40,8 +45,8 @@ public class BrokerageWithdrawController {
40 45 @PutMapping("/approve")
41 46 @Operation(summary = "通过申请")
42 47 @PreAuthorize("@ss.hasPermission('trade:brokerage-withdraw:audit')")
43   - public CommonResult<Boolean> approveBrokerageWithdraw(@RequestParam("id") Integer id) {
44   - brokerageWithdrawService.auditBrokerageWithdraw(id, BrokerageWithdrawStatusEnum.AUDIT_SUCCESS, "");
  48 + public CommonResult<Boolean> approveBrokerageWithdraw(@RequestParam("id") Long id) {
  49 + brokerageWithdrawService.auditBrokerageWithdraw(id, BrokerageWithdrawStatusEnum.AUDIT_SUCCESS, "", getClientIP());
45 50 return success(true);
46 51 }
47 52  
... ... @@ -49,7 +54,7 @@ public class BrokerageWithdrawController {
49 54 @Operation(summary = "驳回申请")
50 55 @PreAuthorize("@ss.hasPermission('trade:brokerage-withdraw:audit')")
51 56 public CommonResult<Boolean> rejectBrokerageWithdraw(@Valid @RequestBody BrokerageWithdrawRejectReqVO reqVO) {
52   - brokerageWithdrawService.auditBrokerageWithdraw(reqVO.getId(), BrokerageWithdrawStatusEnum.AUDIT_FAIL, reqVO.getAuditReason());
  57 + brokerageWithdrawService.auditBrokerageWithdraw(reqVO.getId(), BrokerageWithdrawStatusEnum.AUDIT_FAIL, reqVO.getAuditReason(), getClientIP());
53 58 return success(true);
54 59 }
55 60  
... ... @@ -57,7 +62,7 @@ public class BrokerageWithdrawController {
57 62 @Operation(summary = "获得佣金提现")
58 63 @Parameter(name = "id", description = "编号", required = true, example = "1024")
59 64 @PreAuthorize("@ss.hasPermission('trade:brokerage-withdraw:query')")
60   - public CommonResult<BrokerageWithdrawRespVO> getBrokerageWithdraw(@RequestParam("id") Integer id) {
  65 + public CommonResult<BrokerageWithdrawRespVO> getBrokerageWithdraw(@RequestParam("id") Long id) {
61 66 BrokerageWithdrawDO brokerageWithdraw = brokerageWithdrawService.getBrokerageWithdraw(id);
62 67 return success(BrokerageWithdrawConvert.INSTANCE.convert(brokerageWithdraw));
63 68 }
... ... @@ -75,4 +80,15 @@ public class BrokerageWithdrawController {
75 80 return success(BrokerageWithdrawConvert.INSTANCE.convertPage(pageResult, userMap));
76 81 }
77 82  
  83 + @PostMapping("/update-transfer")
  84 + @Operation(summary = "更新转账订单为转账成功") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
  85 + @PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现
  86 + public CommonResult<Boolean> updateAfterRefund(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) {
  87 + // 目前业务逻辑,不需要做任何事情
  88 + // 当然,退款会有小概率会失败的情况,可以监控失败状态,进行告警
  89 + log.info("[updateAfterRefund][notifyReqDTO({})]", notifyReqDTO);
  90 + brokerageWithdrawService.updateTransfer(Long.parseLong(notifyReqDTO.getMerchantTransferId()), notifyReqDTO.getPayTransferId());
  91 + return success(true);
  92 + }
  93 +
78 94 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/record/BrokerageRecordRespVO.java
... ... @@ -14,7 +14,7 @@ import java.time.LocalDateTime;
14 14 public class BrokerageRecordRespVO extends BrokerageRecordBaseVO {
15 15  
16 16 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28896")
17   - private Integer id;
  17 + private Long id;
18 18  
19 19 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
20 20 private LocalDateTime createTime;
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawRejectReqVO.java
... ... @@ -14,7 +14,7 @@ public class BrokerageWithdrawRejectReqVO {
14 14  
15 15 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7161")
16 16 @NotNull(message = "编号不能为空")
17   - private Integer id;
  17 + private Long id;
18 18  
19 19 @Schema(description = "审核驳回原因", example = "不对")
20 20 @NotEmpty(message = "审核驳回原因不能为空")
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawRespVO.java
... ... @@ -14,7 +14,7 @@ import java.time.LocalDateTime;
14 14 public class BrokerageWithdrawRespVO extends BrokerageWithdrawBaseVO {
15 15  
16 16 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7161")
17   - private Integer id;
  17 + private Long id;
18 18  
19 19 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
20 20 private LocalDateTime createTime;
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java
... ... @@ -35,7 +35,7 @@ public interface BrokerageWithdrawMapper extends BaseMapperX&lt;BrokerageWithdrawDO
35 35 .orderByAsc(BrokerageWithdrawDO::getStatus).orderByDesc(BrokerageWithdrawDO::getId));
36 36 }
37 37  
38   - default int updateByIdAndStatus(Integer id, Integer status, BrokerageWithdrawDO updateObj) {
  38 + default int updateByIdAndStatus(Long id, Integer status, BrokerageWithdrawDO updateObj) {
39 39 return update(updateObj, new LambdaUpdateWrapper<BrokerageWithdrawDO>()
40 40 .eq(BrokerageWithdrawDO::getId, id)
41 41 .eq(BrokerageWithdrawDO::getStatus, status));
... ...
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 {
21 21 @Override
22 22 @TenantJob
23 23 public String execute(String param) {
24   - int count = tradeOrderUpdateService.createOrderItemCommentBySystem();
  24 + int count = tradeOrderUpdateService.createOrderItemCommentBySystem();
25 25 return String.format("评论订单 %s 个", count);
26 26 }
27 27  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordService.java
... ... @@ -32,7 +32,7 @@ public interface BrokerageRecordService {
32 32 * @param id 编号
33 33 * @return 佣金记录
34 34 */
35   - BrokerageRecordDO getBrokerageRecord(Integer id);
  35 + BrokerageRecordDO getBrokerageRecord(Long id);
36 36  
37 37 /**
38 38 * 获得佣金记录分页
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageRecordServiceImpl.java
... ... @@ -64,7 +64,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
64 64 private ProductSkuApi productSkuApi;
65 65  
66 66 @Override
67   - public BrokerageRecordDO getBrokerageRecord(Integer id) {
  67 + public BrokerageRecordDO getBrokerageRecord(Long id) {
68 68 return brokerageRecordMapper.selectById(id);
69 69 }
70 70  
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawService.java
... ... @@ -28,7 +28,7 @@ public interface BrokerageWithdrawService {
28 28 * @param status 审核状态
29 29 * @param auditReason 驳回原因
30 30 */
31   - void auditBrokerageWithdraw(Integer id, BrokerageWithdrawStatusEnum status, String auditReason);
  31 + void auditBrokerageWithdraw(Long id, BrokerageWithdrawStatusEnum status, String auditReason, String userIp);
32 32  
33 33 /**
34 34 * 获得佣金提现
... ... @@ -36,7 +36,7 @@ public interface BrokerageWithdrawService {
36 36 * @param id 编号
37 37 * @return 佣金提现
38 38 */
39   - BrokerageWithdrawDO getBrokerageWithdraw(Integer id);
  39 + BrokerageWithdrawDO getBrokerageWithdraw(Long id);
40 40  
41 41 /**
42 42 * 获得佣金提现分页
... ... @@ -77,4 +77,10 @@ public interface BrokerageWithdrawService {
77 77 return convertMap(getWithdrawSummaryListByUserId(userIds, status), BrokerageWithdrawSummaryRespBO::getUserId);
78 78 }
79 79  
  80 + /**
  81 + *
  82 + * @param merchantTransferId 提现编号
  83 + * @param payTransferId 转账订单编号
  84 + */
  85 + void updateTransfer(Long merchantTransferId, Long payTransferId);
80 86 }
... ...
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java
... ... @@ -4,20 +4,35 @@ import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.date.LocalDateTimeUtil;
5 5 import cn.hutool.core.map.MapUtil;
6 6 import cn.hutool.core.util.ObjectUtil;
  7 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
7 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 9 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
  10 +import cn.iocoder.yudao.module.pay.api.transfer.PayTransferApi;
  11 +import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
  12 +import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
  13 +import cn.iocoder.yudao.module.pay.api.wallet.PayWalletApi;
  14 +import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletCreateReqDto;
  15 +import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
  16 +import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
  17 +import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum;
  18 +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
9 19 import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
10 20 import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
  21 +import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
  22 +import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
  23 +import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
11 24 import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawPageReqVO;
12 25 import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawCreateReqVO;
13 26 import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageWithdrawConvert;
14 27 import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO;
15 28 import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO;
16 29 import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageWithdrawMapper;
  30 +import cn.iocoder.yudao.module.trade.dal.redis.no.TradeNoRedisDAO;
17 31 import cn.iocoder.yudao.module.trade.enums.MessageTemplateConstants;
18 32 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
19 33 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
20 34 import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
  35 +import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
21 36 import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageWithdrawSummaryRespBO;
22 37 import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
23 38 import org.springframework.stereotype.Service;
... ... @@ -27,10 +42,7 @@ import org.springframework.validation.annotation.Validated;
27 42 import jakarta.annotation.Resource;
28 43 import jakarta.validation.Validator;
29 44 import java.time.LocalDateTime;
30   -import java.util.Collection;
31   -import java.util.Collections;
32   -import java.util.List;
33   -import java.util.Map;
  45 +import java.util.*;
34 46  
35 47 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
36 48 import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
... ... @@ -46,6 +58,8 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
46 58  
47 59 @Resource
48 60 private BrokerageWithdrawMapper brokerageWithdrawMapper;
  61 + @Resource
  62 + private TradeNoRedisDAO tradeNoRedisDAO;
49 63  
50 64 @Resource
51 65 private BrokerageRecordService brokerageRecordService;
... ... @@ -54,15 +68,25 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
54 68  
55 69 @Resource
56 70 private NotifyMessageSendApi notifyMessageSendApi;
  71 + @Resource
  72 + private PayTransferApi payTransferApi;
  73 + @Resource
  74 + private SocialUserApi socialUserApi;
  75 + @Resource
  76 + private PayWalletApi payWalletApi;
57 77  
58 78 @Resource
59 79 private Validator validator;
60 80  
  81 + @Resource
  82 + private TradeOrderProperties tradeOrderProperties;
  83 +
61 84 @Override
62 85 @Transactional(rollbackFor = Exception.class)
63   - public void auditBrokerageWithdraw(Integer id, BrokerageWithdrawStatusEnum status, String auditReason) {
  86 + public void auditBrokerageWithdraw(Long id, BrokerageWithdrawStatusEnum status, String auditReason, String userIp) {
64 87 // 1.1 校验存在
65 88 BrokerageWithdrawDO withdraw = validateBrokerageWithdrawExists(id);
  89 +
66 90 // 1.2 校验状态为审核中
67 91 if (ObjectUtil.notEqual(BrokerageWithdrawStatusEnum.AUDITING.getStatus(), withdraw.getStatus())) {
68 92 throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
... ... @@ -80,9 +104,25 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
80 104 templateCode = MessageTemplateConstants.SMS_BROKERAGE_WITHDRAW_AUDIT_APPROVE;
81 105 // 3.1 通过时佣金转余额
82 106 if (BrokerageWithdrawTypeEnum.WALLET.getType().equals(withdraw.getType())) {
83   - // todo 疯狂:
  107 + PayWalletRespDTO wallet = payWalletApi.getWalletByUserId(withdraw.getUserId());
  108 + payWalletApi.addWallet(new PayWalletCreateReqDto()
  109 + .setWalletId(wallet.getId())
  110 + .setBizType(PayWalletBizTypeEnum.WITHDRAW)
  111 + .setBizId(withdraw.getId().toString())
  112 + .setPrice(withdraw.getPrice())
  113 + .setTitle("分佣提现"));
  114 + rows = brokerageWithdrawMapper.updateByIdAndStatus(id, BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus(),
  115 + new BrokerageWithdrawDO().setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus()).setAuditReason(auditReason).setAuditTime(LocalDateTime.now()));
  116 + if (rows == 0) {
  117 + throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
  118 + }
  119 + }else if (BrokerageWithdrawTypeEnum.ALIPAY_SMALL.getType().equals(withdraw.getType())){
  120 + //获取openid
  121 + SocialUserRespDTO socialUser = socialUserApi.getSocialUserByUserId(UserTypeEnum.MEMBER.getValue(), withdraw.getUserId(), SocialTypeEnum.WECHAT_MINI_APP.getType());
  122 + // 微信提现
  123 + PayTransferCreateReqDTO payTransferCreateReqDTO = getPayTransferCreateReqDTO(userIp, withdraw, socialUser);
  124 + payTransferApi.createTransfer(payTransferCreateReqDTO);
84 125 }
85   - // TODO 疯狂:调用转账接口
86 126 } else if (BrokerageWithdrawStatusEnum.AUDIT_FAIL.equals(status)) {
87 127 templateCode = MessageTemplateConstants.SMS_BROKERAGE_WITHDRAW_AUDIT_REJECT;
88 128 // 3.2 驳回时需要退还用户佣金
... ... @@ -93,16 +133,29 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
93 133 }
94 134  
95 135 // 4. 通知用户
96   - Map<String, Object> templateParams = MapUtil.<String, Object>builder()
97   - .put("createTime", LocalDateTimeUtil.formatNormal(withdraw.getCreateTime()))
98   - .put("price", MoneyUtils.fenToYuanStr(withdraw.getPrice()))
99   - .put("reason", auditReason)
100   - .build();
101   - notifyMessageSendApi.sendSingleMessageToMember(new NotifySendSingleToUserReqDTO()
102   - .setUserId(withdraw.getUserId()).setTemplateCode(templateCode).setTemplateParams(templateParams));
  136 +// Map<String, Object> templateParams = MapUtil.<String, Object>builder()
  137 +// .put("createTime", LocalDateTimeUtil.formatNormal(withdraw.getCreateTime()))
  138 +// .put("price", MoneyUtils.fenToYuanStr(withdraw.getPrice()))
  139 +// .put("reason", auditReason)
  140 +// .build();
  141 +// notifyMessageSendApi.sendSingleMessageToMember(new NotifySendSingleToUserReqDTO()
  142 +// .setUserId(withdraw.getUserId()).setTemplateCode(templateCode).setTemplateParams(templateParams));
  143 + }
  144 +
  145 + private PayTransferCreateReqDTO getPayTransferCreateReqDTO(String userIp, BrokerageWithdrawDO withdraw, SocialUserRespDTO socialUser) {
  146 + PayTransferCreateReqDTO payTransferCreateReqDTO = new PayTransferCreateReqDTO();
  147 + payTransferCreateReqDTO.setAppKey(tradeOrderProperties.getPayAppKey());
  148 + payTransferCreateReqDTO.setChannelCode("wx_lite");
  149 + payTransferCreateReqDTO.setUserIp(userIp);
  150 + payTransferCreateReqDTO.setType(PayTransferTypeEnum.WX_BALANCE.getType());
  151 + payTransferCreateReqDTO.setMerchantTransferId(withdraw.getId().toString());
  152 + payTransferCreateReqDTO.setPrice(withdraw.getPrice());
  153 + payTransferCreateReqDTO.setSubject("佣金提现");
  154 + payTransferCreateReqDTO.setOpenid(socialUser.getOpenid());
  155 + return payTransferCreateReqDTO;
103 156 }
104 157  
105   - private BrokerageWithdrawDO validateBrokerageWithdrawExists(Integer id) {
  158 + private BrokerageWithdrawDO validateBrokerageWithdrawExists(Long id) {
106 159 BrokerageWithdrawDO withdraw = brokerageWithdrawMapper.selectById(id);
107 160 if (withdraw == null) {
108 161 throw exception(BROKERAGE_WITHDRAW_NOT_EXISTS);
... ... @@ -111,7 +164,7 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
111 164 }
112 165  
113 166 @Override
114   - public BrokerageWithdrawDO getBrokerageWithdraw(Integer id) {
  167 + public BrokerageWithdrawDO getBrokerageWithdraw(Long id) {
115 168 return brokerageWithdrawMapper.selectById(id);
116 169 }
117 170  
... ... @@ -150,6 +203,23 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
150 203 return brokerageWithdrawMapper.selectCountAndSumPriceByUserIdAndStatus(userIds, status.getStatus());
151 204 }
152 205  
  206 + @Override
  207 + public void updateTransfer(Long id, Long payTransferId) {
  208 + BrokerageWithdrawDO withdraw = validateBrokerageWithdrawExists(id);
  209 + PayTransferRespDTO transfer = payTransferApi.getTransfer(payTransferId);
  210 + if(PayTransferStatusEnum.isSuccess(transfer.getStatus())){
  211 + withdraw.setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus());
  212 + }else if(PayTransferStatusEnum.isPendingStatus(transfer.getStatus())){
  213 + withdraw.setStatus(BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus());
  214 + }else{
  215 + withdraw.setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_FAIL.getStatus());
  216 + // 3.2 驳回时需要退还用户佣金
  217 + brokerageRecordService.addBrokerage(withdraw.getUserId(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT,
  218 + String.valueOf(withdraw.getId()), withdraw.getPrice(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT.getTitle());
  219 + }
  220 + brokerageWithdrawMapper.updateById(withdraw);
  221 + }
  222 +
153 223 /**
154 224 * 计算提现手续费
155 225 *
... ...
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.api.transfer;
2 2  
3 3 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
4 4  
  5 +import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
5 6 import jakarta.validation.Valid;
6 7  
7 8 /**
... ... @@ -19,4 +20,10 @@ public interface PayTransferApi {
19 20 */
20 21 Long createTransfer(@Valid PayTransferCreateReqDTO reqDTO);
21 22  
  23 + /**
  24 + * 获取转账单详细
  25 + * @param id 转账单编号
  26 + * @return 转账单详细
  27 + */
  28 + PayTransferRespDTO getTransfer(Long id);
22 29 }
... ...
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.api.transfer.dto;
  2 +
  3 +import lombok.Data;
  4 +
  5 +@Data
  6 +public class PayTransferRespDTO {
  7 +
  8 + /**
  9 + * 编号
  10 + */
  11 + private Long id;
  12 +
  13 + /**
  14 + * 转账单号
  15 + *
  16 + */
  17 + private String no;
  18 +
  19 + /**
  20 + * 转账金额,单位:分
  21 + */
  22 + private Integer price;
  23 +
  24 + /**
  25 + * 转账状态
  26 + *
  27 + * 枚举 {@link PayTransferStatusRespEnum}
  28 + */
  29 + private Integer status;
  30 +
  31 +
  32 +}
... ...
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.api.wallet;
  2 +
  3 +import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletCreateReqDto;
  4 +import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
  5 +
  6 +/**
  7 + * 钱包 API 接口
  8 + *
  9 + * @author liurulin
  10 + */
  11 +public interface PayWalletApi {
  12 +
  13 + /**
  14 + * 添加钱包
  15 + * @param reqDTO 创建请求
  16 + */
  17 + void addWallet(PayWalletCreateReqDto reqDTO);
  18 +
  19 + /**
  20 + * 根据用户id获取钱包信息
  21 + * @param userId 用户id
  22 + * @return 钱包信息
  23 + */
  24 + PayWalletRespDTO getWalletByUserId(Long userId);
  25 +}
... ...
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletCreateReqDto.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.api.wallet.dto;
  2 +
  3 +import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +@Data
  8 +public class PayWalletCreateReqDto {
  9 +
  10 + /**
  11 + * 钱包编号
  12 + */
  13 + @NotNull(message = "钱包编号不能为空")
  14 + private Long walletId;
  15 +
  16 + /**
  17 + * 关联业务分类
  18 + */
  19 + @NotNull(message = "关联业务分类不能为空")
  20 + private PayWalletBizTypeEnum bizType;
  21 +
  22 + /**
  23 + * 关联业务编号
  24 + */
  25 + @NotNull(message = "关联业务编号不能为空")
  26 + private String bizId;
  27 +
  28 + /**
  29 + * 流水说明
  30 + */
  31 + @NotNull(message = "流水说明不能为空")
  32 + private String title;
  33 +
  34 + /**
  35 + * 交易金额,单位分
  36 + *
  37 + * 正值表示余额增加,负值表示余额减少
  38 + */
  39 + @NotNull(message = "交易金额不能为空")
  40 + private Integer price;
  41 +
  42 +}
... ...
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.api.wallet.dto;
  2 +
  3 +import lombok.Data;
  4 +
  5 +@Data
  6 +public class PayWalletRespDTO {
  7 +
  8 + /**
  9 + * 编号
  10 + */
  11 + private Long id;
  12 +
  13 + /**
  14 + * 余额,单位分
  15 + */
  16 + private Integer balance;
  17 +
  18 +}
... ...
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java
... ... @@ -19,7 +19,8 @@ public enum PayWalletBizTypeEnum implements IntArrayValuable {
19 19 RECHARGE_REFUND(2, "充值退款"),
20 20 PAYMENT(3, "支付"),
21 21 PAYMENT_REFUND(4, "支付退款"),
22   - UPDATE_BALANCE(5, "更新余额");
  22 + UPDATE_BALANCE(5, "更新余额"),
  23 + WITHDRAW(6, "分佣提现");
23 24  
24 25 /**
25 26 * 业务分类
... ... @@ -36,4 +37,5 @@ public enum PayWalletBizTypeEnum implements IntArrayValuable {
36 37 public int[] array() {
37 38 return ARRAYS;
38 39 }
  40 +
39 41 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java
1 1 package cn.iocoder.yudao.module.pay.api.transfer;
2 2  
3 3 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
  4 +import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
  5 +import cn.iocoder.yudao.module.pay.convert.transfer.PayTransferConvert;
  6 +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
4 7 import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
5 8 import org.springframework.stereotype.Service;
6 9 import org.springframework.validation.annotation.Validated;
... ... @@ -24,4 +27,10 @@ public class PayTransferApiImpl implements PayTransferApi {
24 27 return payTransferService.createTransfer(reqDTO);
25 28 }
26 29  
  30 + @Override
  31 + public PayTransferRespDTO getTransfer(Long id) {
  32 + PayTransferDO transfer = payTransferService.getTransfer(id);
  33 + return PayTransferConvert.INSTANCE.convert3(transfer);
  34 + }
  35 +
27 36 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pay.api.wallet;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
  4 +import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletCreateReqDto;
  5 +import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
  6 +import cn.iocoder.yudao.module.pay.convert.wallet.PayWalletConvert;
  7 +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
  8 +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
  9 +import jakarta.annotation.Resource;
  10 +import org.springframework.stereotype.Service;
  11 +
  12 +/**
  13 + * 钱包 API 实现类
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Service
  18 +public class PayWalletApiImpl implements PayWalletApi {
  19 +
  20 + @Resource
  21 + private PayWalletService payWalletService;
  22 +
  23 + @Override
  24 + public void addWallet(PayWalletCreateReqDto reqDTO) {
  25 + //添加钱包金额
  26 + payWalletService.addWalletBalance(reqDTO.getWalletId(), reqDTO.getBizId(), reqDTO.getBizType(), reqDTO.getPrice());
  27 + }
  28 +
  29 + @Override
  30 + public PayWalletRespDTO getWalletByUserId(Long userId) {
  31 + PayWalletDO orCreateWallet = payWalletService.getOrCreateWallet(userId, UserTypeEnum.MEMBER.getValue());
  32 + return PayWalletConvert.INSTANCE.convert03(orCreateWallet);
  33 + }
  34 +}
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppBaseVO.java
... ... @@ -40,4 +40,9 @@ public class PayAppBaseVO {
40 40 @URL(message = "退款结果的回调地址必须为 URL 格式")
41 41 private String refundNotifyUrl;
42 42  
  43 + @Schema(description = "转账结果的回调地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "http://127.0.0.1:48080/refund-callback")
  44 + @NotNull(message = "转账结果的回调地址不能为空")
  45 + @URL(message = "转账结果的回调地址必须为 URL 格式")
  46 + private String transferNotifyUrl;
  47 +
43 48 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java
... ... @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 6 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
7 7 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
8 8 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
  9 +import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
9 10 import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO;
10 11 import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
11 12 import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskRespVO;
... ... @@ -18,6 +19,7 @@ import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
18 19 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
19 20 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
20 21 import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
  22 +import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
21 23 import io.swagger.v3.oas.annotations.Operation;
22 24 import io.swagger.v3.oas.annotations.Parameter;
23 25 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -49,6 +51,8 @@ public class PayNotifyController {
49 51 @Resource
50 52 private PayRefundService refundService;
51 53 @Resource
  54 + private PayTransferService payTransferService;
  55 + @Resource
52 56 private PayNotifyService notifyService;
53 57 @Resource
54 58 private PayAppService appService;
... ... @@ -95,6 +99,26 @@ public class PayNotifyController {
95 99 return "success";
96 100 }
97 101  
  102 + @PostMapping(value = "/transfer/{channelId}")
  103 + @Operation(summary = "支付渠道的统一【转账】回调")
  104 + @PermitAll
  105 + public String notifyTransfer(@PathVariable("channelId") Long channelId,
  106 + @RequestParam(required = false) Map<String, String> params,
  107 + @RequestBody(required = false) String body) {
  108 + log.info("[notifyRefund][channelId({}) 回调数据({}/{})]", channelId, params, body);
  109 + // 1. 校验支付渠道是否存在
  110 + PayClient payClient = channelService.getPayClient(channelId);
  111 + if (payClient == null) {
  112 + log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
  113 + throw exception(CHANNEL_NOT_FOUND);
  114 + }
  115 +
  116 + // 2. 解析通知数据
  117 + PayTransferRespDTO notify = payClient.parseTransferNotify(params, body);
  118 + payTransferService.notifyTransfer(channelId, notify);
  119 + return "success";
  120 + }
  121 +
98 122 @GetMapping("/get-detail")
99 123 @Operation(summary = "获得回调通知的明细")
100 124 @Parameter(name = "id", description = "编号", required = true, example = "1024")
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/transfer/PayTransferConvert.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.pay.convert.transfer;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
5 5 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
  6 +import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
6 7 import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.transfer.PayDemoTransferCreateReqVO;
7 8 import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferCreateReqVO;
8 9 import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageItemRespVO;
... ... @@ -24,7 +25,9 @@ public interface PayTransferConvert {
24 25  
25 26 PayTransferCreateReqDTO convert(PayDemoTransferCreateReqVO vo);
26 27  
27   - PayTransferRespVO convert(PayTransferDO bean);
  28 + PayTransferRespVO convert(PayTransferDO bean);
  29 +
  30 + PayTransferRespDTO convert3(PayTransferDO bean);
28 31  
29 32 PageResult<PayTransferPageItemRespVO> convertPage(PageResult<PayTransferDO> pageResult);
30 33 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/wallet/PayWalletConvert.java
1 1 package cn.iocoder.yudao.module.pay.convert.wallet;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletCreateReqDto;
  5 +import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
4 6 import cn.iocoder.yudao.module.pay.controller.admin.wallet.vo.wallet.PayWalletRespVO;
5 7 import cn.iocoder.yudao.module.pay.controller.app.wallet.vo.wallet.AppPayWalletRespVO;
6 8 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
... ... @@ -18,4 +20,6 @@ public interface PayWalletConvert {
18 20  
19 21 PageResult<PayWalletRespVO> convertPage(PageResult<PayWalletDO> page);
20 22  
  23 + PayWalletRespDTO convert03(PayWalletDO orCreateWallet);
  24 +
21 25 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java
... ... @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 6 import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO;
  7 +import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
7 8 import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
8 9 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
9 10 import org.apache.ibatis.annotations.Mapper;
... ... @@ -44,6 +45,12 @@ public interface PayTransferMapper extends BaseMapperX&lt;PayTransferDO&gt; {
44 45 default List<PayTransferDO> selectListByStatus(Integer status){
45 46 return selectList(PayTransferDO::getStatus, status);
46 47 }
  48 +
  49 + default PayTransferDO selectByAppIdAndNo(Long appId, String no){
  50 + return selectOne(new LambdaQueryWrapperX<PayTransferDO>()
  51 + .eq(PayTransferDO::getAppId, appId)
  52 + .eq(PayTransferDO::getNo, no));
  53 + }
47 54 }
48 55  
49 56  
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/config/PayProperties.java
... ... @@ -40,6 +40,15 @@ public class PayProperties {
40 40 private String refundNotifyUrl;
41 41  
42 42 /**
  43 + * 转账回调地址
  44 + *
  45 + * 实际上,对应的 PayNotifyController 的 notifyTransfer 方法的 URL
  46 + *
  47 + * 回调顺序:支付渠道(支付宝支付、微信支付) => yudao-module-pay 的 transferNotifyUrl 地址 => 业务的 PayAppDO.transferNotifyUrl 地址
  48 + */
  49 + private String transferNotifyUrl;
  50 +
  51 + /**
43 52 * 支付订单 no 的前缀
44 53 */
45 54 @NotEmpty(message = "支付订单 no 的前缀不能为空")
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java
... ... @@ -178,6 +178,11 @@ public class WalletPayClient extends AbstractPayClient&lt;NonePayClientConfig&gt; {
178 178 }
179 179  
180 180 @Override
  181 + protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body) throws Throwable {
  182 + throw new UnsupportedOperationException("未实现");
  183 + }
  184 +
  185 + @Override
181 186 public PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
182 187 throw new UnsupportedOperationException("待实现");
183 188 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java
... ... @@ -78,5 +78,4 @@ public interface PayRefundService {
78 78 * @return 同步到状态的退款数量,包括退款成功、退款失败
79 79 */
80 80 int syncRefund();
81   -
82 81 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java
... ... @@ -193,6 +193,8 @@ public class PayRefundServiceImpl implements PayRefundService {
193 193 TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyRefund(channel, notify));
194 194 }
195 195  
  196 +
  197 +
196 198 /**
197 199 * 通知并更新订单的退款结果
198 200 *
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java
1 1 package cn.iocoder.yudao.module.pay.service.transfer;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
4 5 import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
5 6 import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferCreateReqVO;
6 7 import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO;
... ... @@ -54,4 +55,12 @@ public interface PayTransferService {
54 55 * @return 同步到状态的转账数量,包括转账成功、转账失败、转账中的
55 56 */
56 57 int syncTransfer();
  58 +
  59 + /**
  60 + * 渠道的转账通知
  61 + *
  62 + * @param channelId 渠道编号
  63 + * @param notify 通知
  64 + */
  65 + void notifyTransfer(Long channelId, PayTransferRespDTO notify);
57 66 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java
... ... @@ -21,6 +21,7 @@ import cn.iocoder.yudao.module.pay.dal.mysql.transfer.PayTransferMapper;
21 21 import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
22 22 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
23 23 import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
  24 +import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
24 25 import cn.iocoder.yudao.module.pay.service.app.PayAppService;
25 26 import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
26 27 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
... ... @@ -28,6 +29,7 @@ import jakarta.annotation.Resource;
28 29 import jakarta.validation.Validator;
29 30 import lombok.extern.slf4j.Slf4j;
30 31 import org.springframework.stereotype.Service;
  32 +import org.springframework.transaction.annotation.Propagation;
31 33 import org.springframework.transaction.annotation.Transactional;
32 34  
33 35 import java.util.List;
... ... @@ -51,6 +53,9 @@ public class PayTransferServiceImpl implements PayTransferService {
51 53 private static final String TRANSFER_NO_PREFIX = "T";
52 54  
53 55 @Resource
  56 + private PayProperties payProperties;
  57 +
  58 + @Resource
54 59 private PayTransferMapper transferMapper;
55 60 @Resource
56 61 private PayAppService appService;
... ... @@ -96,13 +101,15 @@ public class PayTransferServiceImpl implements PayTransferService {
96 101 transfer = INSTANCE.convert(reqDTO)
97 102 .setChannelId(channel.getId())
98 103 .setNo(no).setStatus(WAITING.getStatus())
99   - .setNotifyUrl(payApp.getTransferNotifyUrl());
  104 + .setNotifyUrl(payApp.getTransferNotifyUrl())
  105 + .setAppId(channel.getAppId());
100 106 transferMapper.insert(transfer);
101 107 }
102 108 try {
103 109 // 3. 调用三方渠道发起转账
104 110 PayTransferUnifiedReqDTO transferUnifiedReq = INSTANCE.convert2(transfer)
105 111 .setOutTransferNo(transfer.getNo());
  112 + transferUnifiedReq.setNotifyUrl(genChannelTransferNotifyUrl(channel));
106 113 PayTransferRespDTO unifiedTransferResp = client.unifiedTransfer(transferUnifiedReq);
107 114 // 4. 通知转账结果
108 115 getSelf().notifyTransfer(channel, unifiedTransferResp);
... ... @@ -116,6 +123,16 @@ public class PayTransferServiceImpl implements PayTransferService {
116 123 return transfer.getId();
117 124 }
118 125  
  126 + /**
  127 + * 根据支付渠道的编码,生成支付渠道的回调地址
  128 + *
  129 + * @param channel 支付渠道
  130 + * @return 支付渠道的回调地址 配置地址 + "/" + channel id
  131 + */
  132 + private String genChannelTransferNotifyUrl(PayChannelDO channel) {
  133 + return payProperties.getTransferNotifyUrl() + "/" + channel.getId();
  134 + }
  135 +
119 136 private PayTransferDO validateTransferCanCreate(PayTransferCreateReqDTO dto, Long appId) {
120 137 PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantTransferId(appId, dto.getMerchantTransferId());
121 138 if (transfer != null) {
... ... @@ -154,7 +171,8 @@ public class PayTransferServiceImpl implements PayTransferService {
154 171  
155 172 private void notifyTransferInProgress(PayChannelDO channel, PayTransferRespDTO notify) {
156 173 // 1.校验
157   - PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo());
  174 + PayTransferDO transfer = transferMapper.selectByAppIdAndNo(
  175 + channel.getAppId(), notify.getOutTransferNo());
158 176 if (transfer == null) {
159 177 throw exception(PAY_TRANSFER_NOT_FOUND);
160 178 }
... ... @@ -172,16 +190,13 @@ public class PayTransferServiceImpl implements PayTransferService {
172 190 throw exception(PAY_TRANSFER_STATUS_IS_NOT_WAITING);
173 191 }
174 192 log.info("[notifyTransferInProgress][transfer({}) 更新为转账进行中状态]", transfer.getId());
175   -
176   - // 3. 插入转账通知记录
177   - notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(),
178   - transfer.getId());
179 193 }
180 194  
181 195  
182 196 private void notifyTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) {
183 197 // 1.校验
184   - PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo());
  198 + PayTransferDO transfer = transferMapper.selectByAppIdAndNo(
  199 + channel.getAppId(), notify.getOutTransferNo());
185 200 if (transfer == null) {
186 201 throw exception(PAY_TRANSFER_NOT_FOUND);
187 202 }
... ... @@ -210,7 +225,8 @@ public class PayTransferServiceImpl implements PayTransferService {
210 225  
211 226 private void notifyTransferClosed(PayChannelDO channel, PayTransferRespDTO notify) {
212 227 // 1.校验
213   - PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo());
  228 + PayTransferDO transfer = transferMapper.selectByAppIdAndNo(
  229 + channel.getAppId(), notify.getOutTransferNo());
214 230 if (transfer == null) {
215 231 throw exception(PAY_TRANSFER_NOT_FOUND);
216 232 }
... ... @@ -283,7 +299,7 @@ public class PayTransferServiceImpl implements PayTransferService {
283 299 }
284 300 }
285 301  
286   - private void notifyTransfer(Long channelId, PayTransferRespDTO notify) {
  302 + public void notifyTransfer(Long channelId, PayTransferRespDTO notify) {
287 303 // 校验渠道是否有效
288 304 PayChannelDO channel = channelService.validPayChannel(channelId);
289 305 // 通知转账结果给对应的业务
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java
... ... @@ -200,6 +200,9 @@ public class PayWalletServiceImpl implements PayWalletService {
200 200 case UPDATE_BALANCE: // 更新余额
201 201 walletMapper.updateWhenRecharge(payWallet.getId(), price);
202 202 break;
  203 + case WITHDRAW:
  204 + walletMapper.updateWhenRecharge(payWallet.getId(), price);
  205 + break;
203 206 default: {
204 207 // TODO 其它类型待实现
205 208 throw new UnsupportedOperationException("待实现");
... ...
yudao-module-pay/yudao-spring-boot-starter-biz-pay/pom.xml
... ... @@ -64,6 +64,10 @@
64 64 <groupId>com.github.binarywang</groupId>
65 65 <artifactId>weixin-java-pay</artifactId>
66 66 </dependency>
  67 + <dependency>
  68 + <groupId>com.github.binarywang</groupId>
  69 + <artifactId>wx-java-miniapp-spring-boot-starter</artifactId>
  70 + </dependency>
67 71  
68 72 <!-- Test 测试相关 -->
69 73 <dependency>
... ... @@ -71,6 +75,7 @@
71 75 <artifactId>yudao-spring-boot-starter-test</artifactId>
72 76 <scope>test</scope>
73 77 </dependency>
  78 +
74 79 </dependencies>
75 80  
76 81 </project>
... ...
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java
... ... @@ -79,6 +79,8 @@ public interface PayClient {
79 79 */
80 80 PayRefundRespDTO getRefund(String outTradeNo, String outRefundNo);
81 81  
  82 + // ============ 转账相关 ==========
  83 +
82 84 /**
83 85 * 调用渠道,进行转账
84 86 *
... ... @@ -95,4 +97,13 @@ public interface PayClient {
95 97 * @return 转账信息
96 98 */
97 99 PayTransferRespDTO getTransfer(String outTradeNo, PayTransferTypeEnum type);
  100 +
  101 + /**
  102 + * 解析 transfer 回调数据
  103 + *
  104 + * @param params HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数
  105 + * @param body HTTP 回调接口的 request body
  106 + * @return 转账信息
  107 + */
  108 + PayTransferRespDTO parseTransferNotify(Map<String, String> params, String body);
98 109 }
... ...
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java
... ... @@ -9,6 +9,8 @@ import jakarta.validation.constraints.Min;
9 9 import jakarta.validation.constraints.NotBlank;
10 10 import jakarta.validation.constraints.NotEmpty;
11 11 import jakarta.validation.constraints.NotNull;
  12 +import org.hibernate.validator.constraints.URL;
  13 +
12 14 import java.util.Map;
13 15  
14 16 import static cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum.*;
... ... @@ -75,4 +77,11 @@ public class PayTransferUnifiedReqDTO {
75 77 * 支付渠道的额外参数
76 78 */
77 79 private Map<String, String> channelExtras;
  80 +
  81 + /**
  82 + * 转账结果的 notify 回调地址
  83 + */
  84 + @NotEmpty(message = "转账结果的回调地址不能为空")
  85 + @URL(message = "转账结果的 notify 回调地址必须是 URL 格式")
  86 + private String notifyUrl;
78 87 }
... ...
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/WxPayTransferPartnerNotifyV3Result.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.pay.core.client.dto.transfer;
  2 +
  3 +import com.github.binarywang.wxpay.bean.notify.OriginNotifyResponse;
  4 +import com.github.binarywang.wxpay.bean.notify.WxPayBaseNotifyV3Result;
  5 +import com.google.gson.annotations.SerializedName;
  6 +import lombok.Data;
  7 +import lombok.NoArgsConstructor;
  8 +
  9 +import java.io.Serializable;
  10 +
  11 +@NoArgsConstructor
  12 +public class WxPayTransferPartnerNotifyV3Result implements Serializable, WxPayBaseNotifyV3Result<WxPayTransferPartnerNotifyV3Result.TransferNotifyResult> {
  13 +
  14 + private static final long serialVersionUID = -1L;
  15 +
  16 + /**
  17 + * 源数据
  18 + */
  19 + private OriginNotifyResponse rawData;
  20 +
  21 + /**
  22 + * 解密后的数据
  23 + */
  24 + private TransferNotifyResult result;
  25 +
  26 + @Override
  27 + public void setRawData(OriginNotifyResponse rawData) {
  28 + this.rawData = rawData;
  29 + }
  30 +
  31 + @Override
  32 + public void setResult(TransferNotifyResult data) {
  33 + this.result = data;
  34 + }
  35 +
  36 + public TransferNotifyResult getResult() {
  37 + return result;
  38 + }
  39 +
  40 + public OriginNotifyResponse getRawData() {
  41 + return rawData;
  42 + }
  43 +
  44 + @Data
  45 + @NoArgsConstructor
  46 + public static class TransferNotifyResult implements Serializable {
  47 + private static final long serialVersionUID = 1L;
  48 +
  49 + /*********************** 公共字段 ********************
  50 +
  51 + /**
  52 + * 商家批次单号
  53 + */
  54 + @SerializedName(value = "out_batch_no")
  55 + protected String outBatchNo;
  56 +
  57 + /**
  58 + * 微信批次单号
  59 + */
  60 + @SerializedName(value = "batch_id")
  61 + protected String batchId;
  62 +
  63 + /**
  64 + * 批次状态
  65 + */
  66 + @SerializedName(value = "batch_status")
  67 + protected String batchStatus;
  68 +
  69 + /**
  70 + * 批次总笔数
  71 + */
  72 + @SerializedName(value = "total_num")
  73 + protected Integer totalNum;
  74 +
  75 + /**
  76 + * 批次总金额
  77 + */
  78 + @SerializedName(value = "total_amount")
  79 + protected Integer totalAmount;
  80 +
  81 + /**
  82 + * 批次更新时间
  83 + */
  84 + @SerializedName(value = "update_time")
  85 + private String updateTime;
  86 +
  87 + /*********************** FINISHED ********************
  88 +
  89 + /**
  90 + * 转账成功金额
  91 + */
  92 + @SerializedName(value = "success_amount")
  93 + protected Integer successAmount;
  94 +
  95 + /**
  96 + * 转账成功笔数
  97 + */
  98 + @SerializedName(value = "success_num")
  99 + protected Integer successNum;
  100 +
  101 + /**
  102 + * 转账失败金额
  103 + */
  104 + @SerializedName(value = "fail_amount")
  105 + protected Integer failAmount;
  106 +
  107 + /**
  108 + * 转账失败笔数
  109 + */
  110 + @SerializedName(value = "fail_num")
  111 + protected Integer failNum;
  112 +
  113 + /*********************** CLOSED ********************
  114 +
  115 + /**
  116 + * 商户号
  117 + */
  118 + @SerializedName(value = "mchid")
  119 + protected String mchId;
  120 +
  121 + /**
  122 + * 批次关闭原因
  123 + */
  124 + @SerializedName(value = "close_reason")
  125 + protected String closeReason;
  126 +
  127 + }
  128 +}
... ...
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
... ... @@ -220,6 +220,22 @@ public abstract class AbstractPayClient&lt;Config extends PayClientConfig&gt; implemen
220 220 }
221 221  
222 222 @Override
  223 + public final PayTransferRespDTO parseTransferNotify(Map<String, String> params, String body) {
  224 + try {
  225 + return doParseTransferNotify(params, body);
  226 + } catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可
  227 + throw ex;
  228 + } catch (Throwable ex) {
  229 + log.error("[doParseTransferNotify][客户端({}) params({}) body({}) 解析失败]",
  230 + getId(), params, body, ex);
  231 + throw buildPayException(ex);
  232 + }
  233 + }
  234 +
  235 + protected abstract PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body)
  236 + throws Throwable;
  237 +
  238 + @Override
223 239 public final PayTransferRespDTO getTransfer(String outTradeNo, PayTransferTypeEnum type) {
224 240 try {
225 241 return doGetTransfer(outTradeNo, type);
... ...
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,11 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient&lt;AlipayPa
325 325 }
326 326 }
327 327  
  328 + @Override
  329 + protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body) throws Throwable {
  330 + throw new UnsupportedOperationException("未实现");
  331 + }
  332 +
328 333 // ========== 各种工具方法 ==========
329 334  
330 335 protected String formatAmount(Integer amount) {
... ...
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayAppPayClient.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
2 2  
3 3 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
4 4 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
  5 +import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
5 6 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
6 7 import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
7 8 import com.alipay.api.AlipayApiException;
... ... @@ -56,4 +57,5 @@ public class AlipayAppPayClient extends AbstractAlipayPayClient {
56 57 return PayOrderRespDTO.waitingOf(displayMode, response.getBody(),
57 58 reqDTO.getOutTradeNo(), response);
58 59 }
  60 +
59 61 }
... ...
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java
... ... @@ -5,6 +5,7 @@ import cn.hutool.core.map.MapUtil;
5 5 import cn.hutool.core.util.StrUtil;
6 6 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
7 7 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
  8 +import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
8 9 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
9 10 import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
10 11 import com.alipay.api.AlipayApiException;
... ... @@ -82,4 +83,5 @@ public class AlipayBarPayClient extends AbstractAlipayPayClient {
82 83 return PayOrderRespDTO.waitingOf(displayMode, "",
83 84 reqDTO.getOutTradeNo(), response);
84 85 }
  86 +
85 87 }
... ...
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java
... ... @@ -4,6 +4,7 @@ import cn.hutool.core.util.ObjectUtil;
4 4 import cn.hutool.http.Method;
5 5 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
6 6 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
  7 +import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
7 8 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
8 9 import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
9 10 import com.alipay.api.AlipayApiException;
... ... @@ -66,4 +67,5 @@ public class AlipayPcPayClient extends AbstractAlipayPayClient {
66 67 return PayOrderRespDTO.waitingOf(displayMode, response.getBody(),
67 68 reqDTO.getOutTradeNo(), response);
68 69 }
  70 +
69 71 }
... ...
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/mock/MockPayClient.java
... ... @@ -58,6 +58,11 @@ public class MockPayClient extends AbstractPayClient&lt;NonePayClientConfig&gt; {
58 58 }
59 59  
60 60 @Override
  61 + protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body) throws Throwable {
  62 + throw new UnsupportedOperationException("未实现");
  63 + }
  64 +
  65 + @Override
61 66 protected PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) {
62 67 throw new UnsupportedOperationException("模拟支付无退款回调");
63 68 }
... ...
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java
... ... @@ -14,23 +14,26 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
14 14 import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
15 15 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
16 16 import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
  17 +import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.WxPayTransferPartnerNotifyV3Result;
17 18 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
18 19 import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
19 20 import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
20   -import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result;
21   -import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
22   -import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
23   -import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
  21 +import com.github.binarywang.wxpay.bean.notify.*;
24 22 import com.github.binarywang.wxpay.bean.request.*;
25 23 import com.github.binarywang.wxpay.bean.result.*;
  24 +import com.github.binarywang.wxpay.bean.transfer.TransferBatchesRequest;
  25 +import com.github.binarywang.wxpay.bean.transfer.TransferBatchesResult;
26 26 import com.github.binarywang.wxpay.config.WxPayConfig;
27 27 import com.github.binarywang.wxpay.exception.WxPayException;
  28 +import com.github.binarywang.wxpay.service.TransferService;
28 29 import com.github.binarywang.wxpay.service.WxPayService;
29 30 import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
30 31 import lombok.extern.slf4j.Slf4j;
31 32  
32 33 import java.time.LocalDateTime;
33 34 import java.time.ZoneId;
  35 +import java.util.ArrayList;
  36 +import java.util.List;
34 37 import java.util.Map;
35 38 import java.util.Objects;
36 39  
... ... @@ -349,6 +352,29 @@ public abstract class AbstractWxPayClient extends AbstractPayClient&lt;WxPayClientC
349 352 }
350 353  
351 354 @Override
  355 + public PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body) throws WxPayException {
  356 + switch (config.getApiVersion()) {
  357 + case API_VERSION_V3:
  358 + return parseTransferNotifyV3(body);
  359 + default:
  360 + throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
  361 + }
  362 + }
  363 +
  364 + private PayTransferRespDTO parseTransferNotifyV3(String body) throws WxPayException {
  365 + WxPayTransferPartnerNotifyV3Result response = client.baseParseOrderNotifyV3Result(body, null, WxPayTransferPartnerNotifyV3Result.class, WxPayTransferPartnerNotifyV3Result.TransferNotifyResult.class);
  366 + WxPayTransferPartnerNotifyV3Result.TransferNotifyResult result = response.getResult();
  367 + // 2. 构建结果
  368 + if (Objects.equals("FINISHED", result.getBatchStatus())) {
  369 + if(result.getFailNum() <= 0){
  370 + return PayTransferRespDTO.successOf(result.getBatchId(), parseDateV3(result.getUpdateTime()),
  371 + result.getOutBatchNo(), response);
  372 + }
  373 + }
  374 + return PayTransferRespDTO.closedOf(result.getBatchStatus(), result.getCloseReason(), result.getOutBatchNo(), response);
  375 + }
  376 +
  377 + @Override
352 378 protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) throws WxPayException {
353 379 try {
354 380 switch (config.getApiVersion()) {
... ... @@ -426,8 +452,26 @@ public abstract class AbstractWxPayClient extends AbstractPayClient&lt;WxPayClientC
426 452 }
427 453  
428 454 @Override
429   - protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
430   - throw new UnsupportedOperationException("待实现");
  455 + protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws WxPayException {
  456 + TransferService transferService = client.getTransferService();
  457 + List<TransferBatchesRequest.TransferDetail> transferDetailList = new ArrayList<>();
  458 + transferDetailList.add(TransferBatchesRequest.TransferDetail.newBuilder()
  459 + .outDetailNo(reqDTO.getOutTransferNo())
  460 + .transferAmount(reqDTO.getPrice())
  461 + .transferRemark(reqDTO.getSubject())
  462 + .openid(reqDTO.getOpenid())
  463 + .build());
  464 + TransferBatchesRequest transferBatches = TransferBatchesRequest.newBuilder()
  465 + .appid(this.config.getAppId())
  466 + .outBatchNo(reqDTO.getOutTransferNo())
  467 + .batchName(reqDTO.getSubject())
  468 + .batchRemark(reqDTO.getSubject())
  469 + .totalAmount(reqDTO.getPrice())
  470 + .totalNum(1)
  471 + .transferDetailList(transferDetailList).build();
  472 + transferBatches.setNotifyUrl(reqDTO.getNotifyUrl());
  473 + TransferBatchesResult transferBatchesResult = transferService.transferBatches(transferBatches);
  474 + return PayTransferRespDTO.dealingOf(transferBatchesResult.getBatchId(), reqDTO.getOutTransferNo(), transferBatchesResult);
431 475 }
432 476  
433 477 @Override
... ...
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBatchesRequest.java 0 → 100644
  1 +package com.github.binarywang.wxpay.bean.transfer;
  2 +
  3 +import com.github.binarywang.wxpay.v3.SpecEncrypt;
  4 +import com.google.gson.annotations.SerializedName;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Builder;
  7 +import lombok.Data;
  8 +import lombok.NoArgsConstructor;
  9 +
  10 +import java.io.Serializable;
  11 +import java.util.List;
  12 +
  13 +/**
  14 + * 发起商家转账API参数
  15 + *
  16 + * @author zhongjun
  17 + * created on 2022/6/17
  18 + **/
  19 +@Data
  20 +@Builder(builderMethodName = "newBuilder")
  21 +@NoArgsConstructor
  22 +@AllArgsConstructor
  23 +public class TransferBatchesRequest implements Serializable {
  24 + private static final long serialVersionUID = -2175582517588397426L;
  25 +
  26 + /**
  27 + * 直连商户的appid
  28 + */
  29 + @SerializedName("appid")
  30 + private String appid;
  31 +
  32 + /**
  33 + * 商家批次单号
  34 + */
  35 + @SerializedName("out_batch_no")
  36 + private String outBatchNo;
  37 +
  38 + /**
  39 + * 批次名称
  40 + */
  41 + @SerializedName("batch_name")
  42 + private String batchName;
  43 +
  44 + /**
  45 + * 批次备注
  46 + */
  47 + @SerializedName("batch_remark")
  48 + private String batchRemark;
  49 +
  50 + /**
  51 + * 转账总金额
  52 + */
  53 + @SerializedName("total_amount")
  54 + private Integer totalAmount;
  55 +
  56 + /**
  57 + * 转账总笔数
  58 + */
  59 + @SerializedName("total_num")
  60 + private Integer totalNum;
  61 +
  62 + /**
  63 + * 转账明细列表
  64 + */
  65 + @SpecEncrypt
  66 + @SerializedName("transfer_detail_list")
  67 + private List<TransferDetail> transferDetailList;
  68 +
  69 + /**
  70 + * 转账场景ID
  71 + */
  72 + @SerializedName("transfer_scene_id")
  73 + private String transferSceneId;
  74 +
  75 + /**
  76 + * 通知地址 说明:异步接收微信支付结果通知的回调地址,通知url必须为公网可访问的url,必须为https,不能携带参数。
  77 + */
  78 + @SerializedName("notify_url")
  79 + private String notifyUrl;
  80 +
  81 + @Data
  82 + @Builder(builderMethodName = "newBuilder")
  83 + @AllArgsConstructor
  84 + @NoArgsConstructor
  85 + public static class TransferDetail {
  86 +
  87 + /**
  88 + * 商家明细单号
  89 + */
  90 + @SerializedName("out_detail_no")
  91 + private String outDetailNo;
  92 +
  93 + /**
  94 + * 转账金额
  95 + */
  96 + @SerializedName("transfer_amount")
  97 + private Integer transferAmount;
  98 +
  99 + /**
  100 + * 转账备注
  101 + */
  102 + @SerializedName("transfer_remark")
  103 + private String transferRemark;
  104 +
  105 + /**
  106 + * 用户在直连商户应用下的用户标示
  107 + */
  108 + @SerializedName("openid")
  109 + private String openid;
  110 +
  111 + /**
  112 + * 收款用户姓名
  113 + */
  114 + @SpecEncrypt
  115 + @SerializedName("user_name")
  116 + private String userName;
  117 + }
  118 +}
... ...
yudao-server/src/main/resources/application-dev.yaml
... ... @@ -165,7 +165,8 @@ yudao:
165 165 pay:
166 166 order-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址
167 167 refund-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
168   - demo: true # 开启演示模式
  168 + transfer-notify-url: https://yunai.natapp1.cc/admin-api/pay/notify/transfer # 支付渠道的【转账】回调地址
  169 + demo: false # 开启演示模式
169 170 tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
170 171  
171 172 justauth:
... ...
yudao-server/src/main/resources/application-local.yaml
... ... @@ -218,6 +218,7 @@ yudao:
218 218 pay:
219 219 order-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址
220 220 refund-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
  221 + transfer-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/transfer # 支付渠道的【转账】回调地址
221 222 access-log: # 访问日志的配置项
222 223 enable: false
223 224 demo: false # 关闭演示模式
... ...