Commit 38c76806a3da36f8b97a5943c1410c94011623b0

Authored by YunaiV
1 parent f81dc105

feat:【PAY 支付】微信支付的转账,接入新的 API(需要继续测试,= = 真麻烦)

feat:【PAY 支付】钱包支持转账功能
Showing 17 changed files with 249 additions and 205 deletions
yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java
... ... @@ -113,7 +113,7 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
113 113 if (BrokerageWithdrawTypeEnum.WALLET.getType().equals(withdraw.getType())) {
114 114 payWalletApi.addWalletBalance(new PayWalletAddBalanceReqDTO()
115 115 .setUserId(withdraw.getUserId()).setUserType(UserTypeEnum.MEMBER.getValue())
116   - .setBizType(PayWalletBizTypeEnum.BROKERAGE_WITHDRAW.getType()).setBizId(withdraw.getId().toString())
  116 + .setBizType(PayWalletBizTypeEnum.TRANSFER.getType()).setBizId(withdraw.getId().toString())
117 117 .setPrice(withdraw.getPrice()));
118 118 // 1.2 微信 API
119 119 } else if (BrokerageWithdrawTypeEnum.WECHAT_API.getType().equals(withdraw.getType())) {
... ...
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java
1 1 package cn.iocoder.yudao.module.pay.api.transfer.dto;
2 2  
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
3 5 import jakarta.validation.constraints.Min;
4 6 import jakarta.validation.constraints.NotEmpty;
5 7 import jakarta.validation.constraints.NotNull;
6 8 import lombok.Data;
7 9  
  10 +import java.util.ArrayList;
  11 +import java.util.HashMap;
  12 +import java.util.List;
8 13 import java.util.Map;
9 14  
10 15 /**
... ... @@ -70,4 +75,46 @@ public class PayTransferCreateReqDTO {
70 75 */
71 76 private String userName;
72 77  
  78 + /**
  79 + * 【微信】现金营销场景
  80 + *
  81 + * @param activityName 活动名称
  82 + * @param rewardDescription 奖励说明
  83 + * @return channelExtras
  84 + */
  85 + public static Map<String, String> buildWeiXinChannelExtra1000(String activityName, String rewardDescription) {
  86 + return buildWeiXinChannelExtra(1000,
  87 + "活动名称", activityName,
  88 + "奖励说明", rewardDescription);
  89 + }
  90 +
  91 + /**
  92 + * 【微信】企业报销场景
  93 + *
  94 + * @param expenseType 报销类型
  95 + * @param expenseDescription 报销说明
  96 + * @return channelExtras
  97 + */
  98 + public static Map<String, String> buildWeiXinChannelExtra1006(String expenseType, String expenseDescription) {
  99 + return buildWeiXinChannelExtra(1006,
  100 + "报销类型", expenseType,
  101 + "报销说明", expenseDescription);
  102 + }
  103 +
  104 + private static Map<String, String> buildWeiXinChannelExtra(Integer sceneId, String... values) {
  105 + Map<String, String> channelExtras = new HashMap<>();
  106 + // 构建场景报备信息列表
  107 + List<Map<String, String>> sceneReportInfos = new ArrayList<>();
  108 + for (int i = 0; i < values.length; i += 2) {
  109 + Map<String, String> info = new HashMap<>();
  110 + info.put("infoType", values[i]);
  111 + info.put("infoContent", values[i + 1]);
  112 + sceneReportInfos.add(info);
  113 + }
  114 + // 设置场景ID和场景报备信息
  115 + channelExtras.put("sceneId", StrUtil.toString(sceneId));
  116 + channelExtras.put("sceneReportInfos", JsonUtils.toJsonString(sceneReportInfos));
  117 + return channelExtras;
  118 + }
  119 +
73 120 }
... ...
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java
... ... @@ -20,7 +20,7 @@ public enum PayWalletBizTypeEnum implements ArrayValuable&lt;Integer&gt; {
20 20 PAYMENT(3, "支付"),
21 21 PAYMENT_REFUND(4, "支付退款"),
22 22 UPDATE_BALANCE(5, "更新余额"),
23   - BROKERAGE_WITHDRAW(6, "分佣提现");
  23 + TRANSFER(6, "转账");
24 24  
25 25 /**
26 26 * 业务分类
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http
1   -### 请求 /pay/pay/demo-order 接口 => 成功
  1 +### 请求 /pay/pay/demo-order 接口(支付宝) => 成功
2 2 POST {{baseUrl}}/pay/demo-withdraw/create
3 3 Authorization: Bearer {{token}}
4 4 Content-Type: application/json
... ... @@ -10,4 +10,31 @@ tenant-id: {{adminTenantId}}
10 10 "price": 10,
11 11 "userAccount": "oespxk7368@sandbox.com",
12 12 "userName": "oespxk7368"
  13 +}
  14 +
  15 +### 请求 /pay/pay/demo-order 接口(微信余额) => 成功
  16 +POST {{baseUrl}}/pay/demo-withdraw/create
  17 +Authorization: Bearer {{token}}
  18 +Content-Type: application/json
  19 +tenant-id: {{adminTenantId}}
  20 +
  21 +{
  22 + "type": 2,
  23 + "subject": "测试转账",
  24 + "price": 1,
  25 + "userAccount": "oiSC85elO_OZogXODC5RoGyXamK4",
  26 + "userName": "芋艿"
  27 +}
  28 +
  29 +### 请求 /pay/pay/demo-order 接口(钱包余额) => 成功
  30 +POST {{baseUrl}}/pay/demo-withdraw/create
  31 +Authorization: Bearer {{token}}
  32 +Content-Type: application/json
  33 +tenant-id: {{adminTenantId}}
  34 +
  35 +{
  36 + "type": 3,
  37 + "subject": "测试转账",
  38 + "price": 1,
  39 + "userAccount": "1"
13 40 }
14 41 \ No newline at end of file
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawCreateReqVO.java
... ... @@ -27,7 +27,6 @@ public class PayDemoWithdrawCreateReqVO {
27 27 private String userAccount;
28 28  
29 29 @Schema(description = "收款人姓名", example = "test1")
30   - @NotBlank(message = "收款人姓名不能为空")
31 30 private String userName;
32 31  
33 32 @Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java
... ... @@ -12,10 +12,12 @@ import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
12 12 import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
13 13 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
14 14 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
  15 +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
15 16 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
16 17 import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient;
17 18 import cn.iocoder.yudao.module.pay.service.app.PayAppService;
18 19 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
  20 +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
19 21 import com.google.common.collect.Maps;
20 22 import io.swagger.v3.oas.annotations.Operation;
21 23 import io.swagger.v3.oas.annotations.Parameter;
... ... @@ -51,6 +53,8 @@ public class PayOrderController {
51 53 private PayOrderService orderService;
52 54 @Resource
53 55 private PayAppService appService;
  56 + @Resource
  57 + private PayWalletService payWalletService;
54 58  
55 59 @GetMapping("/get")
56 60 @Operation(summary = "获得支付订单")
... ... @@ -92,11 +96,11 @@ public class PayOrderController {
92 96 public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
93 97 // 1. 钱包支付事,需要额外传 user_id 和 user_type
94 98 if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) {
95   - Map<String, String> channelExtras = reqVO.getChannelExtras() == null ?
96   - Maps.newHashMapWithExpectedSize(2) : reqVO.getChannelExtras();
97   - channelExtras.put(WalletPayClient.USER_ID_KEY, String.valueOf(getLoginUserId()));
98   - channelExtras.put(WalletPayClient.USER_TYPE_KEY, String.valueOf(getLoginUserType()));
99   - reqVO.setChannelExtras(channelExtras);
  99 + if (reqVO.getChannelExtras() == null) {
  100 + reqVO.setChannelExtras(Maps.newHashMapWithExpectedSize(1));
  101 + }
  102 + PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), getLoginUserType());
  103 + reqVO.getChannelExtras().put(WalletPayClient.WALLET_ID_KEY, String.valueOf(wallet.getId()));
100 104 }
101 105  
102 106 // 2. 提交支付
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java
... ... @@ -9,9 +9,11 @@ import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitReqV
9 9 import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
10 10 import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
11 11 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
  12 +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
12 13 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
13 14 import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient;
14 15 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
  16 +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
15 17 import com.google.common.collect.Maps;
16 18 import io.swagger.v3.oas.annotations.Operation;
17 19 import io.swagger.v3.oas.annotations.Parameter;
... ... @@ -22,7 +24,6 @@ import lombok.extern.slf4j.Slf4j;
22 24 import org.springframework.validation.annotation.Validated;
23 25 import org.springframework.web.bind.annotation.*;
24 26  
25   -import java.util.Map;
26 27 import java.util.Objects;
27 28  
28 29 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
... ... @@ -39,6 +40,8 @@ public class AppPayOrderController {
39 40  
40 41 @Resource
41 42 private PayOrderService payOrderService;
  43 + @Resource
  44 + private PayWalletService payWalletService;
42 45  
43 46 @GetMapping("/get")
44 47 @Operation(summary = "获得支付订单")
... ... @@ -63,11 +66,11 @@ public class AppPayOrderController {
63 66 public CommonResult<AppPayOrderSubmitRespVO> submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) {
64 67 // 1. 钱包支付事,需要额外传 user_id 和 user_type
65 68 if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) {
66   - Map<String, String> channelExtras = reqVO.getChannelExtras() == null ?
67   - Maps.newHashMapWithExpectedSize(2) : reqVO.getChannelExtras();
68   - channelExtras.put(WalletPayClient.USER_ID_KEY, String.valueOf(getLoginUserId()));
69   - channelExtras.put(WalletPayClient.USER_TYPE_KEY, String.valueOf(getLoginUserType()));
70   - reqVO.setChannelExtras(channelExtras);
  69 + if (reqVO.getChannelExtras() == null) {
  70 + reqVO.setChannelExtras(Maps.newHashMapWithExpectedSize(1));
  71 + }
  72 + PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), getLoginUserType());
  73 + reqVO.getChannelExtras().put(WalletPayClient.WALLET_ID_KEY, String.valueOf(wallet.getId()));
71 74 }
72 75  
73 76 // 2. 提交支付
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java
... ... @@ -53,6 +53,10 @@ public interface PayTransferMapper extends BaseMapperX&lt;PayTransferDO&gt; {
53 53 PayTransferDO::getNo, no);
54 54 }
55 55  
  56 + default PayTransferDO selectByNo(String no) {
  57 + return selectOne(PayTransferDO::getNo, no);
  58 + }
  59 +
56 60 }
57 61  
58 62  
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java
... ... @@ -14,13 +14,16 @@ import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
14 14 import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
15 15 import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
16 16 import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
  17 +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum;
17 18 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
18 19 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
  20 +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO;
19 21 import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO;
20 22 import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
21 23 import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
22 24 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
23 25 import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
  26 +import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
24 27 import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
25 28 import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService;
26 29 import lombok.extern.slf4j.Slf4j;
... ... @@ -39,13 +42,14 @@ import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.REFUND_NOT_FO
39 42 @Slf4j
40 43 public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
41 44  
42   - public static final String USER_ID_KEY = "user_id";
43   - public static final String USER_TYPE_KEY = "user_type";
  45 + public static final String WALLET_ID_KEY = "walletId";
44 46  
45 47 private PayWalletService wallService;
46 48 private PayWalletTransactionService walletTransactionService;
  49 +
47 50 private PayOrderService orderService;
48 51 private PayRefundService refundService;
  52 + private PayTransferService transferService;
49 53  
50 54 public WalletPayClient(Long channelId, NonePayClientConfig config) {
51 55 super(channelId, PayChannelEnum.WALLET.getCode(), config);
... ... @@ -62,19 +66,18 @@ public class WalletPayClient extends AbstractPayClient&lt;NonePayClientConfig&gt; {
62 66 }
63 67  
64 68 @Override
  69 + @SuppressWarnings("PatternVariableCanBeUsed")
65 70 protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
66 71 try {
67   - Long userId = MapUtil.getLong(reqDTO.getChannelExtras(), USER_ID_KEY);
68   - Integer userType = MapUtil.getInt(reqDTO.getChannelExtras(), USER_TYPE_KEY);
69   - Assert.notNull(userId, "用户 id 不能为空");
70   - Assert.notNull(userType, "用户类型不能为空");
71   - PayWalletTransactionDO transaction = wallService.orderPay(userId, userType, reqDTO.getOutTradeNo(),
72   - reqDTO.getPrice());
  72 + Long walletId = MapUtil.getLong(reqDTO.getChannelExtras(), WALLET_ID_KEY);
  73 + Assert.notNull(walletId, "钱包编号");
  74 + PayWalletTransactionDO transaction = wallService.orderPay(walletId,
  75 + reqDTO.getOutTradeNo(), reqDTO.getPrice());
73 76 return PayOrderRespDTO.successOf(transaction.getNo(), transaction.getCreator(),
74 77 transaction.getCreateTime(),
75 78 reqDTO.getOutTradeNo(), transaction);
76 79 } catch (Throwable ex) {
77   - log.error("[doUnifiedOrder] 失败", ex);
  80 + log.error("[doUnifiedOrder][reqDTO({}) 异常]", reqDTO, ex);
78 81 Integer errorCode = INTERNAL_SERVER_ERROR.getCode();
79 82 String errorMsg = INTERNAL_SERVER_ERROR.getMsg();
80 83 if (ex instanceof ServiceException) {
... ... @@ -122,6 +125,7 @@ public class WalletPayClient extends AbstractPayClient&lt;NonePayClientConfig&gt; {
122 125 }
123 126  
124 127 @Override
  128 + @SuppressWarnings("PatternVariableCanBeUsed")
125 129 protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
126 130 try {
127 131 PayWalletTransactionDO payWalletTransaction = wallService.orderRefund(reqDTO.getOutRefundNo(),
... ... @@ -129,7 +133,7 @@ public class WalletPayClient extends AbstractPayClient&lt;NonePayClientConfig&gt; {
129 133 return PayRefundRespDTO.successOf(payWalletTransaction.getNo(), payWalletTransaction.getCreateTime(),
130 134 reqDTO.getOutRefundNo(), payWalletTransaction);
131 135 } catch (Throwable ex) {
132   - log.error("[doUnifiedRefund] 失败", ex);
  136 + log.error("[doUnifiedRefund][reqDOT({}) 异常]", reqDTO, ex);
133 137 Integer errorCode = INTERNAL_SERVER_ERROR.getCode();
134 138 String errorMsg = INTERNAL_SERVER_ERROR.getMsg();
135 139 if (ex instanceof ServiceException) {
... ... @@ -177,18 +181,71 @@ public class WalletPayClient extends AbstractPayClient&lt;NonePayClientConfig&gt; {
177 181 }
178 182  
179 183 @Override
180   - protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) {
181   - throw new UnsupportedOperationException("未实现");
  184 + @SuppressWarnings("PatternVariableCanBeUsed")
  185 + public PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
  186 + try {
  187 + Long walletId = Long.parseLong(reqDTO.getUserAccount());
  188 + PayWalletTransactionDO transaction = wallService.addWalletBalance(walletId, String.valueOf(reqDTO.getOutTransferNo()),
  189 + PayWalletBizTypeEnum.TRANSFER, reqDTO.getPrice());
  190 + return PayTransferRespDTO.successOf(transaction.getNo(), transaction.getCreateTime(),
  191 + reqDTO.getOutTransferNo(), transaction);
  192 + } catch (Throwable ex) {
  193 + log.error("[doUnifiedTransfer][reqDTO({}) 异常]", reqDTO, ex);
  194 + Integer errorCode = INTERNAL_SERVER_ERROR.getCode();
  195 + String errorMsg = INTERNAL_SERVER_ERROR.getMsg();
  196 + if (ex instanceof ServiceException) {
  197 + ServiceException serviceException = (ServiceException) ex;
  198 + errorCode = serviceException.getCode();
  199 + errorMsg = serviceException.getMessage();
  200 + }
  201 + return PayTransferRespDTO.closedOf(String.valueOf(errorCode), errorMsg,
  202 + reqDTO.getOutTransferNo(), "");
  203 + }
182 204 }
183 205  
184 206 @Override
185   - public PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
186   - throw new UnsupportedOperationException("待实现");
  207 + protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) {
  208 + throw new UnsupportedOperationException("钱包支付无转账回调");
187 209 }
188 210  
189 211 @Override
190 212 protected PayTransferRespDTO doGetTransfer(String outTradeNo) {
191   - throw new UnsupportedOperationException("待实现");
  213 + if (transferService == null) {
  214 + transferService = SpringUtil.getBean(PayTransferService.class);
  215 + }
  216 + // 获取转账单
  217 + PayTransferDO transfer = transferService.getTransferByNo(outTradeNo);
  218 + // 转账单不存在,返回关闭状态
  219 + if (transfer == null) {
  220 + return PayTransferRespDTO.closedOf(String.valueOf(PAY_ORDER_EXTENSION_NOT_FOUND.getCode()),
  221 + PAY_ORDER_EXTENSION_NOT_FOUND.getMsg(), outTradeNo, "");
  222 + }
  223 + // 关闭状态
  224 + if (PayTransferStatusRespEnum.isClosed(transfer.getStatus())) {
  225 + return PayTransferRespDTO.closedOf(transfer.getChannelErrorCode(),
  226 + transfer.getChannelErrorMsg(), outTradeNo, "");
  227 + }
  228 + // 成功状态
  229 + if (PayTransferStatusRespEnum.isSuccess(transfer.getStatus())) {
  230 + PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransaction(
  231 + String.valueOf(transfer.getId()), PayWalletBizTypeEnum.TRANSFER);
  232 + Assert.notNull(walletTransaction, "转账单 {} 钱包流水不能为空", outTradeNo);
  233 + return PayTransferRespDTO.successOf(walletTransaction.getNo(), walletTransaction.getCreateTime(),
  234 + outTradeNo, walletTransaction);
  235 + }
  236 + // 处理中状态
  237 + if (PayTransferStatusRespEnum.isProcessing(transfer.getStatus())) {
  238 + return PayTransferRespDTO.processingOf(transfer.getChannelTransferNo(),
  239 + outTradeNo, transfer);
  240 + }
  241 + // 等待状态
  242 + if (transfer.getStatus().equals(PayTransferStatusRespEnum.WAITING.getStatus())) {
  243 + return PayTransferRespDTO.waitingOf(transfer.getChannelTransferNo(),
  244 + outTradeNo, transfer);
  245 + }
  246 + // 其它状态为无效状态
  247 + log.error("[doGetTransfer] 转账单 {} 的状态不正确", outTradeNo);
  248 + throw new IllegalStateException(String.format("转账单[%s] 状态不正确", outTradeNo));
192 249 }
193 250  
194 251 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java
... ... @@ -57,11 +57,16 @@ public class PayDemoTransferServiceImpl implements PayDemoWithdrawService {
57 57 demoTransferMapper.insert(withdraw);
58 58  
59 59 // 2.1 创建支付单
60   - Long payTransferId = payTransferApi.createTransfer(new PayTransferCreateReqDTO()
  60 + PayTransferCreateReqDTO transferReqDTO = new PayTransferCreateReqDTO()
61 61 .setAppKey(PAY_APP_KEY).setChannelCode(withdraw.getTransferChannelCode()).setUserIp(getClientIP()) // 支付应用
62 62 .setMerchantOrderId(String.valueOf(withdraw.getId())) // 业务的订单编号
63 63 .setSubject(reqVO.getSubject()).setPrice(withdraw.getPrice()) // 价格信息
64   - .setUserAccount(reqVO.getUserAccount()).setUserName(reqVO.getUserName())); // 收款信息
  64 + .setUserAccount(reqVO.getUserAccount()).setUserName(reqVO.getUserName()); // 收款信息
  65 + if (ObjectUtil.equal(reqVO.getType(), PayDemoWithdrawTypeEnum.WECHAT.getType())) {
  66 + transferReqDTO.setChannelExtras(PayTransferCreateReqDTO.buildWeiXinChannelExtra1000(
  67 + "测试活动", "测试奖励"));
  68 + }
  69 + Long payTransferId = payTransferApi.createTransfer(transferReqDTO);
65 70 // 2.2 更新转账单到 demo 示例提现单
66 71 demoTransferMapper.updateById(new PayDemoWithdrawDO().setId(withdraw.getId())
67 72 .setPayTransferId(payTransferId));
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java
... ... @@ -29,6 +29,14 @@ public interface PayTransferService {
29 29 PayTransferDO getTransfer(Long id);
30 30  
31 31 /**
  32 + * 根据转账单号获取转账单
  33 + *
  34 + * @param no 转账单号
  35 + * @return 转账单
  36 + */
  37 + PayTransferDO getTransferByNo(String no);
  38 +
  39 + /**
32 40 * 获得转账单分页
33 41 *
34 42 * @param pageReqVO 分页查询
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java
... ... @@ -239,6 +239,11 @@ public class PayTransferServiceImpl implements PayTransferService {
239 239 }
240 240  
241 241 @Override
  242 + public PayTransferDO getTransferByNo(String no) {
  243 + return transferMapper.selectByNo(no);
  244 + }
  245 +
  246 + @Override
242 247 public PageResult<PayTransferDO> getTransferPage(PayTransferPageReqVO pageReqVO) {
243 248 return transferMapper.selectPage(pageReqVO);
244 249 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java
... ... @@ -41,12 +41,11 @@ public interface PayWalletService {
41 41 /**
42 42 * 钱包订单支付
43 43 *
44   - * @param userId 用户 id
45   - * @param userType 用户类型
  44 + * @param walletId 钱包编号
46 45 * @param outTradeNo 外部订单号
47 46 * @param price 金额
48 47 */
49   - PayWalletTransactionDO orderPay(Long userId, Integer userType, String outTradeNo, Integer price);
  48 + PayWalletTransactionDO orderPay(Long walletId, String outTradeNo, Integer price);
50 49  
51 50 /**
52 51 * 钱包订单支付退款
... ... @@ -60,8 +59,8 @@ public interface PayWalletService {
60 59 /**
61 60 * 扣减钱包余额
62 61 *
63   - * @param walletId 钱包 id
64   - * @param bizId 业务关联 id
  62 + * @param walletId 钱包编号
  63 + * @param bizId 业务关联编号
65 64 * @param bizType 业务关联分类
66 65 * @param price 扣减金额
67 66 * @return 钱包流水
... ... @@ -72,8 +71,8 @@ public interface PayWalletService {
72 71 /**
73 72 * 增加钱包余额
74 73 *
75   - * @param walletId 钱包 id
76   - * @param bizId 业务关联 id
  74 + * @param walletId 钱包编号
  75 + * @param bizId 业务关联编号
77 76 * @param bizType 业务关联分类
78 77 * @param price 增加金额
79 78 * @return 钱包流水
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java
... ... @@ -81,13 +81,13 @@ public class PayWalletServiceImpl implements PayWalletService {
81 81  
82 82 @Override
83 83 @Transactional(rollbackFor = Exception.class)
84   - public PayWalletTransactionDO orderPay(Long userId, Integer userType, String outTradeNo, Integer price) {
  84 + public PayWalletTransactionDO orderPay(Long walletId, String outTradeNo, Integer price) {
85 85 // 1. 判断支付交易拓展单是否存
86 86 PayOrderExtensionDO orderExtension = orderService.getOrderExtensionByNo(outTradeNo);
87 87 if (orderExtension == null) {
88 88 throw exception(PAY_ORDER_EXTENSION_NOT_FOUND);
89 89 }
90   - PayWalletDO wallet = getOrCreateWallet(userId, userType);
  90 + PayWalletDO wallet = walletMapper.selectById(walletId);
91 91 // 2. 扣减余额
92 92 return reduceWalletBalance(wallet.getId(), orderExtension.getOrderId(), PAYMENT, price);
93 93 }
... ... @@ -198,7 +198,7 @@ public class PayWalletServiceImpl implements PayWalletService {
198 198 break;
199 199 }
200 200 case UPDATE_BALANCE: // 更新余额
201   - case BROKERAGE_WITHDRAW: // 分佣提现
  201 + case TRANSFER: // 分佣提现
202 202 walletMapper.updateWhenAdd(payWallet.getId(), price);
203 203 break;
204 204 default: {
... ...
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java
... ... @@ -58,6 +58,8 @@ public class PayTransferUnifiedReqDTO {
58 58  
59 59 /**
60 60 * 支付渠道的额外参数
  61 + *
  62 + * 微信支付:sceneId 和 scene_report_infos 字段,必须传递;参考 <a href="https://pay.weixin.qq.com/doc/v3/merchant/4012711988#%EF%BC%883%EF%BC%89%E6%8C%89%E8%BD%AC%E8%B4%A6%E5%9C%BA%E6%99%AF%E6%8A%A5%E5%A4%87%E8%83%8C%E6%99%AF%E4%BF%A1%E6%81%AF">按转账场景报备背景信息</>
61 63 */
62 64 private Map<String, String> channelExtras;
63 65  
... ...
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java
... ... @@ -7,6 +7,7 @@ import cn.hutool.core.date.LocalDateTimeUtil;
7 7 import cn.hutool.core.date.TemporalAccessorUtil;
8 8 import cn.hutool.core.util.StrUtil;
9 9 import cn.iocoder.yudao.framework.common.util.io.FileUtils;
  10 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
10 11 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
11 12 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
12 13 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
... ... @@ -20,10 +21,9 @@ import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
20 21 import com.github.binarywang.wxpay.bean.notify.*;
21 22 import com.github.binarywang.wxpay.bean.request.*;
22 23 import com.github.binarywang.wxpay.bean.result.*;
23   -import com.github.binarywang.wxpay.bean.transfer.QueryTransferBatchesRequest;
24   -import com.github.binarywang.wxpay.bean.transfer.QueryTransferBatchesResult;
25   -import com.github.binarywang.wxpay.bean.transfer.TransferBatchesRequest;
26   -import com.github.binarywang.wxpay.bean.transfer.TransferBatchesResult;
  24 +import com.github.binarywang.wxpay.bean.transfer.TransferBillsGetResult;
  25 +import com.github.binarywang.wxpay.bean.transfer.TransferBillsRequest;
  26 +import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
27 27 import com.github.binarywang.wxpay.config.WxPayConfig;
28 28 import com.github.binarywang.wxpay.exception.WxPayException;
29 29 import com.github.binarywang.wxpay.service.WxPayService;
... ... @@ -32,8 +32,6 @@ import lombok.extern.slf4j.Slf4j;
32 32  
33 33 import java.time.LocalDateTime;
34 34 import java.time.ZoneId;
35   -import java.util.Collections;
36   -import java.util.List;
37 35 import java.util.Map;
38 36 import java.util.Objects;
39 37  
... ... @@ -463,53 +461,57 @@ public abstract class AbstractWxPayClient extends AbstractPayClient&lt;WxPayClientC
463 461  
464 462 @Override
465 463 protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws WxPayException {
466   - // 1. 构建 TransferBatchesRequest 请求
467   - List<TransferBatchesRequest.TransferDetail> transferDetailList = Collections.singletonList(
468   - TransferBatchesRequest.TransferDetail.newBuilder()
469   - .outDetailNo(reqDTO.getOutTransferNo())
470   - .transferAmount(reqDTO.getPrice())
471   - .transferRemark(reqDTO.getSubject())
472   - .openid(reqDTO.getUserAccount())
473   - .build());
474   - // TODO @luchi:能不能我们搞个 TransferBatchesRequestX extends TransferBatchesRequest,这样更简洁一点。
475   - TransferBatchesRequest transferBatches = TransferBatchesRequest.newBuilder()
  464 + // 1. 构建 TransferBillsRequest 请求
  465 + TransferBillsRequest request = TransferBillsRequest.newBuilder()
476 466 .appid(this.config.getAppId())
477   - .outBatchNo(reqDTO.getOutTransferNo())
478   - .batchName(reqDTO.getSubject())
479   - .batchRemark(reqDTO.getSubject())
480   - .totalAmount(reqDTO.getPrice())
481   - .totalNum(transferDetailList.size())
482   - .transferDetailList(transferDetailList).build()
483   - .setNotifyUrl(reqDTO.getNotifyUrl());
  467 + .outBillNo(reqDTO.getOutTransferNo())
  468 + .transferAmount(reqDTO.getPrice())
  469 + .transferRemark(reqDTO.getSubject())
  470 + .transferSceneId(reqDTO.getChannelExtras().get("sceneId"))
  471 + .openid(reqDTO.getUserAccount())
  472 + .userName(reqDTO.getUserName())
  473 + .transferSceneReportInfos(JsonUtils.parseArray(reqDTO.getChannelExtras().get("sceneReportInfos"),
  474 + TransferBillsRequest.TransferSceneReportInfo.class))
  475 + .notifyUrl(reqDTO.getNotifyUrl())
  476 + .build();
  477 + // 特殊:微信转账,必须 0.3 元起,才允许传入姓名
  478 + if (reqDTO.getPrice() < 30) {
  479 + request.setUserName(null);
  480 + }
  481 +
484 482 // 2.1 执行请求
485   - TransferBatchesResult transferBatchesResult = client.getTransferService().transferBatches(transferBatches);
486   - // 2.2 创建返回结果
487   - return PayTransferRespDTO.processingOf(transferBatchesResult.getBatchId(), reqDTO.getOutTransferNo(), transferBatchesResult);
  483 + try {
  484 + TransferBillsResult response = client.getTransferService().transferBills(request);
  485 + System.out.println(response);
  486 +
  487 + // 2.2 创建返回结果
  488 + // TODO @芋艿:这里要解析下;
  489 + return PayTransferRespDTO.processingOf(response.getTransferBillNo(), reqDTO.getOutTransferNo(), response);
  490 + } catch (WxPayException e) {
  491 + log.error("[doUnifiedTransfer][转账({}) 发起微信支付异常", reqDTO, e);
  492 + String errorCode = getErrorCode(e);
  493 + String errorMessage = getErrorMessage(e);
  494 + return PayTransferRespDTO.closedOf(errorCode, errorMessage,
  495 + reqDTO.getOutTransferNo(), e.getXmlString());
  496 + }
488 497 }
489 498  
490 499 @Override
491 500 protected PayTransferRespDTO doGetTransfer(String outTradeNo) throws WxPayException {
492   - QueryTransferBatchesRequest request = QueryTransferBatchesRequest.newBuilder()
493   - .outBatchNo(outTradeNo).needQueryDetail(true).offset(0).limit(20).detailStatus("ALL")
494   - .build();
495   - QueryTransferBatchesResult response = client.getTransferService().transferBatchesOutBatchNo(request);
496   - QueryTransferBatchesResult.TransferBatch transferBatch = response.getTransferBatch();
497   - if (Objects.equals("FINISHED", transferBatch.getBatchStatus())) {
498   - // 明细中全部成功则成功,任一失败则失败
499   - if (response.getTransferDetailList().stream().allMatch(detail -> Objects.equals("SUCCESS", detail.getDetailStatus()))) {
500   - return PayTransferRespDTO.successOf(transferBatch.getBatchId(), parseDateV3(transferBatch.getUpdateTime()),
501   - transferBatch.getOutBatchNo(), response);
502   - }
503   - if (response.getTransferDetailList().stream().anyMatch(detail -> Objects.equals("FAIL", detail.getDetailStatus()))) {
504   - return PayTransferRespDTO.closedOf(transferBatch.getBatchStatus(), transferBatch.getCloseReason(),
505   - transferBatch.getOutBatchNo(), response);
506   - }
  501 + // 1. 执行请求
  502 + TransferBillsGetResult response = client.getTransferService().getBillsByTransferBillNo(outTradeNo);
  503 +
  504 + // 2. 创建返回结果
  505 + String state = response.getState();
  506 + if (ObjectUtils.equalsAny(state, "ACCEPTED", "PROCESSING", "WAIT_USER_CONFIRM", "TRANSFERING")) {
  507 + return PayTransferRespDTO.processingOf(response.getTransferBillNo(), response.getOutBillNo(), response);
507 508 }
508   - if (Objects.equals("CLOSED", transferBatch.getBatchStatus())) {
509   - return PayTransferRespDTO.closedOf(transferBatch.getBatchStatus(), transferBatch.getCloseReason(),
510   - transferBatch.getOutBatchNo(), response);
  509 + if (Objects.equals("SUCCESS", state)) {
  510 + return PayTransferRespDTO.successOf(response.getTransferBillNo(), parseDateV3(response.getUpdateTime()),
  511 + response.getOutBillNo(), response);
511 512 }
512   - return PayTransferRespDTO.processingOf(transferBatch.getBatchId(), transferBatch.getOutBatchNo(), response);
  513 + return PayTransferRespDTO.closedOf(state, response.getFailReason(),
  514 + response.getOutBillNo(), response);
513 515 }
514 516  
515 517 // ========== 各种工具方法 ==========
... ...
yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBatchesRequest.java deleted
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   -}