From 38c76806a3da36f8b97a5943c1410c94011623b0 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 8 May 2025 23:31:23 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E3=80=90PAY=20=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E3=80=91=E5=BE=AE=E4=BF=A1=E6=94=AF=E4=BB=98=E7=9A=84=E8=BD=AC?= =?UTF-8?q?=E8=B4=A6=EF=BC=8C=E6=8E=A5=E5=85=A5=E6=96=B0=E7=9A=84=20API?= =?UTF-8?q?=EF=BC=88=E9=9C=80=E8=A6=81=E7=BB=A7=E7=BB=AD=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=EF=BC=8C=3D=20=3D=20=E7=9C=9F=E9=BA=BB=E7=83=A6=EF=BC=89=20fea?= =?UTF-8?q?t=EF=BC=9A=E3=80=90PAY=20=E6=94=AF=E4=BB=98=E3=80=91=E9=92=B1?= =?UTF-8?q?=E5=8C=85=E6=94=AF=E6=8C=81=E8=BD=AC=E8=B4=A6=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BrokerageWithdrawServiceImpl.java | 2 +- .../transfer/dto/PayTransferCreateReqDTO.java | 47 +++++++ .../enums/wallet/PayWalletBizTypeEnum.java | 2 +- .../admin/demo/PayDemoWithdrawController.http | 29 ++++- .../withdraw/PayDemoWithdrawCreateReqVO.java | 1 - .../admin/order/PayOrderController.java | 14 ++- .../app/order/AppPayOrderController.java | 15 ++- .../dal/mysql/transfer/PayTransferMapper.java | 4 + .../framework/pay/core/WalletPayClient.java | 87 ++++++++++--- .../demo/PayDemoTransferServiceImpl.java | 9 +- .../service/transfer/PayTransferService.java | 8 ++ .../transfer/PayTransferServiceImpl.java | 5 + .../pay/service/wallet/PayWalletService.java | 13 +- .../service/wallet/PayWalletServiceImpl.java | 6 +- .../transfer/PayTransferUnifiedReqDTO.java | 2 + .../impl/weixin/AbstractWxPayClient.java | 92 +++++++------- .../bean/transfer/TransferBatchesRequest.java | 118 ------------------ 17 files changed, 249 insertions(+), 205 deletions(-) delete mode 100644 yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBatchesRequest.java diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java index 39fe8765eb..240e771cf8 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java +++ b/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 { if (BrokerageWithdrawTypeEnum.WALLET.getType().equals(withdraw.getType())) { payWalletApi.addWalletBalance(new PayWalletAddBalanceReqDTO() .setUserId(withdraw.getUserId()).setUserType(UserTypeEnum.MEMBER.getValue()) - .setBizType(PayWalletBizTypeEnum.BROKERAGE_WITHDRAW.getType()).setBizId(withdraw.getId().toString()) + .setBizType(PayWalletBizTypeEnum.TRANSFER.getType()).setBizId(withdraw.getId().toString()) .setPrice(withdraw.getPrice())); // 1.2 微信 API } else if (BrokerageWithdrawTypeEnum.WECHAT_API.getType().equals(withdraw.getType())) { diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java index c07e16b425..ce92080191 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferCreateReqDTO.java @@ -1,10 +1,15 @@ package cn.iocoder.yudao.module.pay.api.transfer.dto; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -70,4 +75,46 @@ public class PayTransferCreateReqDTO { */ private String userName; + /** + * 【微信】现金营销场景 + * + * @param activityName 活动名称 + * @param rewardDescription 奖励说明 + * @return channelExtras + */ + public static Map buildWeiXinChannelExtra1000(String activityName, String rewardDescription) { + return buildWeiXinChannelExtra(1000, + "活动名称", activityName, + "奖励说明", rewardDescription); + } + + /** + * 【微信】企业报销场景 + * + * @param expenseType 报销类型 + * @param expenseDescription 报销说明 + * @return channelExtras + */ + public static Map buildWeiXinChannelExtra1006(String expenseType, String expenseDescription) { + return buildWeiXinChannelExtra(1006, + "报销类型", expenseType, + "报销说明", expenseDescription); + } + + private static Map buildWeiXinChannelExtra(Integer sceneId, String... values) { + Map channelExtras = new HashMap<>(); + // 构建场景报备信息列表 + List> sceneReportInfos = new ArrayList<>(); + for (int i = 0; i < values.length; i += 2) { + Map info = new HashMap<>(); + info.put("infoType", values[i]); + info.put("infoContent", values[i + 1]); + sceneReportInfos.add(info); + } + // 设置场景ID和场景报备信息 + channelExtras.put("sceneId", StrUtil.toString(sceneId)); + channelExtras.put("sceneReportInfos", JsonUtils.toJsonString(sceneReportInfos)); + return channelExtras; + } + } diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java index 30336da76e..f2987cfe21 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/wallet/PayWalletBizTypeEnum.java +++ b/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 { PAYMENT(3, "支付"), PAYMENT_REFUND(4, "支付退款"), UPDATE_BALANCE(5, "更新余额"), - BROKERAGE_WITHDRAW(6, "分佣提现"); + TRANSFER(6, "转账"); /** * 业务分类 diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http index 2426c26243..fa18693cf2 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http @@ -1,4 +1,4 @@ -### 请求 /pay/pay/demo-order 接口 => 成功 +### 请求 /pay/pay/demo-order 接口(支付宝) => 成功 POST {{baseUrl}}/pay/demo-withdraw/create Authorization: Bearer {{token}} Content-Type: application/json @@ -10,4 +10,31 @@ tenant-id: {{adminTenantId}} "price": 10, "userAccount": "oespxk7368@sandbox.com", "userName": "oespxk7368" +} + +### 请求 /pay/pay/demo-order 接口(微信余额) => 成功 +POST {{baseUrl}}/pay/demo-withdraw/create +Authorization: Bearer {{token}} +Content-Type: application/json +tenant-id: {{adminTenantId}} + +{ + "type": 2, + "subject": "测试转账", + "price": 1, + "userAccount": "oiSC85elO_OZogXODC5RoGyXamK4", + "userName": "芋艿" +} + +### 请求 /pay/pay/demo-order 接口(钱包余额) => 成功 +POST {{baseUrl}}/pay/demo-withdraw/create +Authorization: Bearer {{token}} +Content-Type: application/json +tenant-id: {{adminTenantId}} + +{ + "type": 3, + "subject": "测试转账", + "price": 1, + "userAccount": "1" } \ No newline at end of file diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawCreateReqVO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawCreateReqVO.java index 56837a3725..de03533648 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/withdraw/PayDemoWithdrawCreateReqVO.java +++ b/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 { private String userAccount; @Schema(description = "收款人姓名", example = "test1") - @NotBlank(message = "收款人姓名不能为空") private String userName; @Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java index 1716d8ae9a..7a6f893bb8 100755 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java +++ b/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; import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient; import cn.iocoder.yudao.module.pay.service.app.PayAppService; import cn.iocoder.yudao.module.pay.service.order.PayOrderService; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; import com.google.common.collect.Maps; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -51,6 +53,8 @@ public class PayOrderController { private PayOrderService orderService; @Resource private PayAppService appService; + @Resource + private PayWalletService payWalletService; @GetMapping("/get") @Operation(summary = "获得支付订单") @@ -92,11 +96,11 @@ public class PayOrderController { public CommonResult submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) { // 1. 钱包支付事,需要额外传 user_id 和 user_type if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) { - Map channelExtras = reqVO.getChannelExtras() == null ? - Maps.newHashMapWithExpectedSize(2) : reqVO.getChannelExtras(); - channelExtras.put(WalletPayClient.USER_ID_KEY, String.valueOf(getLoginUserId())); - channelExtras.put(WalletPayClient.USER_TYPE_KEY, String.valueOf(getLoginUserType())); - reqVO.setChannelExtras(channelExtras); + if (reqVO.getChannelExtras() == null) { + reqVO.setChannelExtras(Maps.newHashMapWithExpectedSize(1)); + } + PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), getLoginUserType()); + reqVO.getChannelExtras().put(WalletPayClient.WALLET_ID_KEY, String.valueOf(wallet.getId())); } // 2. 提交支付 diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java index 7a8bb89870..1af25d3575 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java +++ b/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 import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO; import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient; import cn.iocoder.yudao.module.pay.service.order.PayOrderService; +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; import com.google.common.collect.Maps; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -22,7 +24,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.Map; import java.util.Objects; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -39,6 +40,8 @@ public class AppPayOrderController { @Resource private PayOrderService payOrderService; + @Resource + private PayWalletService payWalletService; @GetMapping("/get") @Operation(summary = "获得支付订单") @@ -63,11 +66,11 @@ public class AppPayOrderController { public CommonResult submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) { // 1. 钱包支付事,需要额外传 user_id 和 user_type if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) { - Map channelExtras = reqVO.getChannelExtras() == null ? - Maps.newHashMapWithExpectedSize(2) : reqVO.getChannelExtras(); - channelExtras.put(WalletPayClient.USER_ID_KEY, String.valueOf(getLoginUserId())); - channelExtras.put(WalletPayClient.USER_TYPE_KEY, String.valueOf(getLoginUserType())); - reqVO.setChannelExtras(channelExtras); + if (reqVO.getChannelExtras() == null) { + reqVO.setChannelExtras(Maps.newHashMapWithExpectedSize(1)); + } + PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), getLoginUserType()); + reqVO.getChannelExtras().put(WalletPayClient.WALLET_ID_KEY, String.valueOf(wallet.getId())); } // 2. 提交支付 diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java index 4e7c90e937..a3ee56c6e8 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/transfer/PayTransferMapper.java +++ b/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 { PayTransferDO::getNo, no); } + default PayTransferDO selectByNo(String no) { + return selectOne(PayTransferDO::getNo, no); + } + } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java index d9a6511aae..933c2a1617 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/framework/pay/core/WalletPayClient.java +++ b/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; import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig; import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum; +import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum; import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; +import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletTransactionDO; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; import cn.iocoder.yudao.module.pay.service.order.PayOrderService; import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; +import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService; import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService; import lombok.extern.slf4j.Slf4j; @@ -39,13 +42,14 @@ import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.REFUND_NOT_FO @Slf4j public class WalletPayClient extends AbstractPayClient { - public static final String USER_ID_KEY = "user_id"; - public static final String USER_TYPE_KEY = "user_type"; + public static final String WALLET_ID_KEY = "walletId"; private PayWalletService wallService; private PayWalletTransactionService walletTransactionService; + private PayOrderService orderService; private PayRefundService refundService; + private PayTransferService transferService; public WalletPayClient(Long channelId, NonePayClientConfig config) { super(channelId, PayChannelEnum.WALLET.getCode(), config); @@ -62,19 +66,18 @@ public class WalletPayClient extends AbstractPayClient { } @Override + @SuppressWarnings("PatternVariableCanBeUsed") protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { try { - Long userId = MapUtil.getLong(reqDTO.getChannelExtras(), USER_ID_KEY); - Integer userType = MapUtil.getInt(reqDTO.getChannelExtras(), USER_TYPE_KEY); - Assert.notNull(userId, "用户 id 不能为空"); - Assert.notNull(userType, "用户类型不能为空"); - PayWalletTransactionDO transaction = wallService.orderPay(userId, userType, reqDTO.getOutTradeNo(), - reqDTO.getPrice()); + Long walletId = MapUtil.getLong(reqDTO.getChannelExtras(), WALLET_ID_KEY); + Assert.notNull(walletId, "钱包编号"); + PayWalletTransactionDO transaction = wallService.orderPay(walletId, + reqDTO.getOutTradeNo(), reqDTO.getPrice()); return PayOrderRespDTO.successOf(transaction.getNo(), transaction.getCreator(), transaction.getCreateTime(), reqDTO.getOutTradeNo(), transaction); } catch (Throwable ex) { - log.error("[doUnifiedOrder] 失败", ex); + log.error("[doUnifiedOrder][reqDTO({}) 异常]", reqDTO, ex); Integer errorCode = INTERNAL_SERVER_ERROR.getCode(); String errorMsg = INTERNAL_SERVER_ERROR.getMsg(); if (ex instanceof ServiceException) { @@ -122,6 +125,7 @@ public class WalletPayClient extends AbstractPayClient { } @Override + @SuppressWarnings("PatternVariableCanBeUsed") protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) { try { PayWalletTransactionDO payWalletTransaction = wallService.orderRefund(reqDTO.getOutRefundNo(), @@ -129,7 +133,7 @@ public class WalletPayClient extends AbstractPayClient { return PayRefundRespDTO.successOf(payWalletTransaction.getNo(), payWalletTransaction.getCreateTime(), reqDTO.getOutRefundNo(), payWalletTransaction); } catch (Throwable ex) { - log.error("[doUnifiedRefund] 失败", ex); + log.error("[doUnifiedRefund][reqDOT({}) 异常]", reqDTO, ex); Integer errorCode = INTERNAL_SERVER_ERROR.getCode(); String errorMsg = INTERNAL_SERVER_ERROR.getMsg(); if (ex instanceof ServiceException) { @@ -177,18 +181,71 @@ public class WalletPayClient extends AbstractPayClient { } @Override - protected PayTransferRespDTO doParseTransferNotify(Map params, String body, Map headers) { - throw new UnsupportedOperationException("未实现"); + @SuppressWarnings("PatternVariableCanBeUsed") + public PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { + try { + Long walletId = Long.parseLong(reqDTO.getUserAccount()); + PayWalletTransactionDO transaction = wallService.addWalletBalance(walletId, String.valueOf(reqDTO.getOutTransferNo()), + PayWalletBizTypeEnum.TRANSFER, reqDTO.getPrice()); + return PayTransferRespDTO.successOf(transaction.getNo(), transaction.getCreateTime(), + reqDTO.getOutTransferNo(), transaction); + } catch (Throwable ex) { + log.error("[doUnifiedTransfer][reqDTO({}) 异常]", reqDTO, ex); + Integer errorCode = INTERNAL_SERVER_ERROR.getCode(); + String errorMsg = INTERNAL_SERVER_ERROR.getMsg(); + if (ex instanceof ServiceException) { + ServiceException serviceException = (ServiceException) ex; + errorCode = serviceException.getCode(); + errorMsg = serviceException.getMessage(); + } + return PayTransferRespDTO.closedOf(String.valueOf(errorCode), errorMsg, + reqDTO.getOutTransferNo(), ""); + } } @Override - public PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { - throw new UnsupportedOperationException("待实现"); + protected PayTransferRespDTO doParseTransferNotify(Map params, String body, Map headers) { + throw new UnsupportedOperationException("钱包支付无转账回调"); } @Override protected PayTransferRespDTO doGetTransfer(String outTradeNo) { - throw new UnsupportedOperationException("待实现"); + if (transferService == null) { + transferService = SpringUtil.getBean(PayTransferService.class); + } + // 获取转账单 + PayTransferDO transfer = transferService.getTransferByNo(outTradeNo); + // 转账单不存在,返回关闭状态 + if (transfer == null) { + return PayTransferRespDTO.closedOf(String.valueOf(PAY_ORDER_EXTENSION_NOT_FOUND.getCode()), + PAY_ORDER_EXTENSION_NOT_FOUND.getMsg(), outTradeNo, ""); + } + // 关闭状态 + if (PayTransferStatusRespEnum.isClosed(transfer.getStatus())) { + return PayTransferRespDTO.closedOf(transfer.getChannelErrorCode(), + transfer.getChannelErrorMsg(), outTradeNo, ""); + } + // 成功状态 + if (PayTransferStatusRespEnum.isSuccess(transfer.getStatus())) { + PayWalletTransactionDO walletTransaction = walletTransactionService.getWalletTransaction( + String.valueOf(transfer.getId()), PayWalletBizTypeEnum.TRANSFER); + Assert.notNull(walletTransaction, "转账单 {} 钱包流水不能为空", outTradeNo); + return PayTransferRespDTO.successOf(walletTransaction.getNo(), walletTransaction.getCreateTime(), + outTradeNo, walletTransaction); + } + // 处理中状态 + if (PayTransferStatusRespEnum.isProcessing(transfer.getStatus())) { + return PayTransferRespDTO.processingOf(transfer.getChannelTransferNo(), + outTradeNo, transfer); + } + // 等待状态 + if (transfer.getStatus().equals(PayTransferStatusRespEnum.WAITING.getStatus())) { + return PayTransferRespDTO.waitingOf(transfer.getChannelTransferNo(), + outTradeNo, transfer); + } + // 其它状态为无效状态 + log.error("[doGetTransfer] 转账单 {} 的状态不正确", outTradeNo); + throw new IllegalStateException(String.format("转账单[%s] 状态不正确", outTradeNo)); } } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java index 083e49a032..f562c81694 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java +++ b/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 { demoTransferMapper.insert(withdraw); // 2.1 创建支付单 - Long payTransferId = payTransferApi.createTransfer(new PayTransferCreateReqDTO() + PayTransferCreateReqDTO transferReqDTO = new PayTransferCreateReqDTO() .setAppKey(PAY_APP_KEY).setChannelCode(withdraw.getTransferChannelCode()).setUserIp(getClientIP()) // 支付应用 .setMerchantOrderId(String.valueOf(withdraw.getId())) // 业务的订单编号 .setSubject(reqVO.getSubject()).setPrice(withdraw.getPrice()) // 价格信息 - .setUserAccount(reqVO.getUserAccount()).setUserName(reqVO.getUserName())); // 收款信息 + .setUserAccount(reqVO.getUserAccount()).setUserName(reqVO.getUserName()); // 收款信息 + if (ObjectUtil.equal(reqVO.getType(), PayDemoWithdrawTypeEnum.WECHAT.getType())) { + transferReqDTO.setChannelExtras(PayTransferCreateReqDTO.buildWeiXinChannelExtra1000( + "测试活动", "测试奖励")); + } + Long payTransferId = payTransferApi.createTransfer(transferReqDTO); // 2.2 更新转账单到 demo 示例提现单 demoTransferMapper.updateById(new PayDemoWithdrawDO().setId(withdraw.getId()) .setPayTransferId(payTransferId)); diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java index fa216f8f90..6692c2ef25 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java @@ -28,6 +28,14 @@ public interface PayTransferService { */ PayTransferDO getTransfer(Long id); + /** + * 根据转账单号获取转账单 + * + * @param no 转账单号 + * @return 转账单 + */ + PayTransferDO getTransferByNo(String no); + /** * 获得转账单分页 * diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java index a3c5f2de9e..5d1b07d7e0 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java @@ -238,6 +238,11 @@ public class PayTransferServiceImpl implements PayTransferService { return transferMapper.selectById(id); } + @Override + public PayTransferDO getTransferByNo(String no) { + return transferMapper.selectByNo(no); + } + @Override public PageResult getTransferPage(PayTransferPageReqVO pageReqVO) { return transferMapper.selectPage(pageReqVO); diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java index 358d149da3..6efdc92626 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletService.java +++ b/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 { /** * 钱包订单支付 * - * @param userId 用户 id - * @param userType 用户类型 + * @param walletId 钱包编号 * @param outTradeNo 外部订单号 * @param price 金额 */ - PayWalletTransactionDO orderPay(Long userId, Integer userType, String outTradeNo, Integer price); + PayWalletTransactionDO orderPay(Long walletId, String outTradeNo, Integer price); /** * 钱包订单支付退款 @@ -60,8 +59,8 @@ public interface PayWalletService { /** * 扣减钱包余额 * - * @param walletId 钱包 id - * @param bizId 业务关联 id + * @param walletId 钱包编号 + * @param bizId 业务关联编号 * @param bizType 业务关联分类 * @param price 扣减金额 * @return 钱包流水 @@ -72,8 +71,8 @@ public interface PayWalletService { /** * 增加钱包余额 * - * @param walletId 钱包 id - * @param bizId 业务关联 id + * @param walletId 钱包编号 + * @param bizId 业务关联编号 * @param bizType 业务关联分类 * @param price 增加金额 * @return 钱包流水 diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java index 30afb437d3..0005acef63 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/wallet/PayWalletServiceImpl.java +++ b/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 { @Override @Transactional(rollbackFor = Exception.class) - public PayWalletTransactionDO orderPay(Long userId, Integer userType, String outTradeNo, Integer price) { + public PayWalletTransactionDO orderPay(Long walletId, String outTradeNo, Integer price) { // 1. 判断支付交易拓展单是否存 PayOrderExtensionDO orderExtension = orderService.getOrderExtensionByNo(outTradeNo); if (orderExtension == null) { throw exception(PAY_ORDER_EXTENSION_NOT_FOUND); } - PayWalletDO wallet = getOrCreateWallet(userId, userType); + PayWalletDO wallet = walletMapper.selectById(walletId); // 2. 扣减余额 return reduceWalletBalance(wallet.getId(), orderExtension.getOrderId(), PAYMENT, price); } @@ -198,7 +198,7 @@ public class PayWalletServiceImpl implements PayWalletService { break; } case UPDATE_BALANCE: // 更新余额 - case BROKERAGE_WITHDRAW: // 分佣提现 + case TRANSFER: // 分佣提现 walletMapper.updateWhenAdd(payWallet.getId(), price); break; default: { diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java index db43821445..1982f12735 100644 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java +++ b/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 { /** * 支付渠道的额外参数 + * + * 微信支付:sceneId 和 scene_report_infos 字段,必须传递;参考 按转账场景报备背景信息 */ private Map channelExtras; diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java index c9cabf7b45..3f5ff93188 100644 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java +++ b/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; import cn.hutool.core.date.TemporalAccessorUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.io.FileUtils; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO; 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; import com.github.binarywang.wxpay.bean.notify.*; import com.github.binarywang.wxpay.bean.request.*; import com.github.binarywang.wxpay.bean.result.*; -import com.github.binarywang.wxpay.bean.transfer.QueryTransferBatchesRequest; -import com.github.binarywang.wxpay.bean.transfer.QueryTransferBatchesResult; -import com.github.binarywang.wxpay.bean.transfer.TransferBatchesRequest; -import com.github.binarywang.wxpay.bean.transfer.TransferBatchesResult; +import com.github.binarywang.wxpay.bean.transfer.TransferBillsGetResult; +import com.github.binarywang.wxpay.bean.transfer.TransferBillsRequest; +import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult; import com.github.binarywang.wxpay.config.WxPayConfig; import com.github.binarywang.wxpay.exception.WxPayException; import com.github.binarywang.wxpay.service.WxPayService; @@ -32,8 +32,6 @@ import lombok.extern.slf4j.Slf4j; import java.time.LocalDateTime; import java.time.ZoneId; -import java.util.Collections; -import java.util.List; import java.util.Map; import java.util.Objects; @@ -463,53 +461,57 @@ public abstract class AbstractWxPayClient extends AbstractPayClient transferDetailList = Collections.singletonList( - TransferBatchesRequest.TransferDetail.newBuilder() - .outDetailNo(reqDTO.getOutTransferNo()) - .transferAmount(reqDTO.getPrice()) - .transferRemark(reqDTO.getSubject()) - .openid(reqDTO.getUserAccount()) - .build()); - // TODO @luchi:能不能我们搞个 TransferBatchesRequestX extends TransferBatchesRequest,这样更简洁一点。 - TransferBatchesRequest transferBatches = TransferBatchesRequest.newBuilder() + // 1. 构建 TransferBillsRequest 请求 + TransferBillsRequest request = TransferBillsRequest.newBuilder() .appid(this.config.getAppId()) - .outBatchNo(reqDTO.getOutTransferNo()) - .batchName(reqDTO.getSubject()) - .batchRemark(reqDTO.getSubject()) - .totalAmount(reqDTO.getPrice()) - .totalNum(transferDetailList.size()) - .transferDetailList(transferDetailList).build() - .setNotifyUrl(reqDTO.getNotifyUrl()); + .outBillNo(reqDTO.getOutTransferNo()) + .transferAmount(reqDTO.getPrice()) + .transferRemark(reqDTO.getSubject()) + .transferSceneId(reqDTO.getChannelExtras().get("sceneId")) + .openid(reqDTO.getUserAccount()) + .userName(reqDTO.getUserName()) + .transferSceneReportInfos(JsonUtils.parseArray(reqDTO.getChannelExtras().get("sceneReportInfos"), + TransferBillsRequest.TransferSceneReportInfo.class)) + .notifyUrl(reqDTO.getNotifyUrl()) + .build(); + // 特殊:微信转账,必须 0.3 元起,才允许传入姓名 + if (reqDTO.getPrice() < 30) { + request.setUserName(null); + } + // 2.1 执行请求 - TransferBatchesResult transferBatchesResult = client.getTransferService().transferBatches(transferBatches); - // 2.2 创建返回结果 - return PayTransferRespDTO.processingOf(transferBatchesResult.getBatchId(), reqDTO.getOutTransferNo(), transferBatchesResult); + try { + TransferBillsResult response = client.getTransferService().transferBills(request); + System.out.println(response); + + // 2.2 创建返回结果 + // TODO @芋艿:这里要解析下; + return PayTransferRespDTO.processingOf(response.getTransferBillNo(), reqDTO.getOutTransferNo(), response); + } catch (WxPayException e) { + log.error("[doUnifiedTransfer][转账({}) 发起微信支付异常", reqDTO, e); + String errorCode = getErrorCode(e); + String errorMessage = getErrorMessage(e); + return PayTransferRespDTO.closedOf(errorCode, errorMessage, + reqDTO.getOutTransferNo(), e.getXmlString()); + } } @Override protected PayTransferRespDTO doGetTransfer(String outTradeNo) throws WxPayException { - QueryTransferBatchesRequest request = QueryTransferBatchesRequest.newBuilder() - .outBatchNo(outTradeNo).needQueryDetail(true).offset(0).limit(20).detailStatus("ALL") - .build(); - QueryTransferBatchesResult response = client.getTransferService().transferBatchesOutBatchNo(request); - QueryTransferBatchesResult.TransferBatch transferBatch = response.getTransferBatch(); - if (Objects.equals("FINISHED", transferBatch.getBatchStatus())) { - // 明细中全部成功则成功,任一失败则失败 - if (response.getTransferDetailList().stream().allMatch(detail -> Objects.equals("SUCCESS", detail.getDetailStatus()))) { - return PayTransferRespDTO.successOf(transferBatch.getBatchId(), parseDateV3(transferBatch.getUpdateTime()), - transferBatch.getOutBatchNo(), response); - } - if (response.getTransferDetailList().stream().anyMatch(detail -> Objects.equals("FAIL", detail.getDetailStatus()))) { - return PayTransferRespDTO.closedOf(transferBatch.getBatchStatus(), transferBatch.getCloseReason(), - transferBatch.getOutBatchNo(), response); - } + // 1. 执行请求 + TransferBillsGetResult response = client.getTransferService().getBillsByTransferBillNo(outTradeNo); + + // 2. 创建返回结果 + String state = response.getState(); + if (ObjectUtils.equalsAny(state, "ACCEPTED", "PROCESSING", "WAIT_USER_CONFIRM", "TRANSFERING")) { + return PayTransferRespDTO.processingOf(response.getTransferBillNo(), response.getOutBillNo(), response); } - if (Objects.equals("CLOSED", transferBatch.getBatchStatus())) { - return PayTransferRespDTO.closedOf(transferBatch.getBatchStatus(), transferBatch.getCloseReason(), - transferBatch.getOutBatchNo(), response); + if (Objects.equals("SUCCESS", state)) { + return PayTransferRespDTO.successOf(response.getTransferBillNo(), parseDateV3(response.getUpdateTime()), + response.getOutBillNo(), response); } - return PayTransferRespDTO.processingOf(transferBatch.getBatchId(), transferBatch.getOutBatchNo(), response); + return PayTransferRespDTO.closedOf(state, response.getFailReason(), + response.getOutBillNo(), response); } // ========== 各种工具方法 ========== diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBatchesRequest.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBatchesRequest.java deleted file mode 100644 index 72296daa83..0000000000 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/com/github/binarywang/wxpay/bean/transfer/TransferBatchesRequest.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.github.binarywang.wxpay.bean.transfer; - -import com.github.binarywang.wxpay.v3.SpecEncrypt; -import com.google.gson.annotations.SerializedName; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; -import java.util.List; - -/** - * 发起商家转账API参数 - * - * @author zhongjun - * created on 2022/6/17 - **/ -@Data -@Builder(builderMethodName = "newBuilder") -@NoArgsConstructor -@AllArgsConstructor -public class TransferBatchesRequest implements Serializable { - private static final long serialVersionUID = -2175582517588397426L; - - /** - * 直连商户的appid - */ - @SerializedName("appid") - private String appid; - - /** - * 商家批次单号 - */ - @SerializedName("out_batch_no") - private String outBatchNo; - - /** - * 批次名称 - */ - @SerializedName("batch_name") - private String batchName; - - /** - * 批次备注 - */ - @SerializedName("batch_remark") - private String batchRemark; - - /** - * 转账总金额 - */ - @SerializedName("total_amount") - private Integer totalAmount; - - /** - * 转账总笔数 - */ - @SerializedName("total_num") - private Integer totalNum; - - /** - * 转账明细列表 - */ - @SpecEncrypt - @SerializedName("transfer_detail_list") - private List transferDetailList; - - /** - * 转账场景ID - */ - @SerializedName("transfer_scene_id") - private String transferSceneId; - - /** - * 通知地址 说明:异步接收微信支付结果通知的回调地址,通知url必须为公网可访问的url,必须为https,不能携带参数。 - */ - @SerializedName("notify_url") - private String notifyUrl; - - @Data - @Builder(builderMethodName = "newBuilder") - @AllArgsConstructor - @NoArgsConstructor - public static class TransferDetail { - - /** - * 商家明细单号 - */ - @SerializedName("out_detail_no") - private String outDetailNo; - - /** - * 转账金额 - */ - @SerializedName("transfer_amount") - private Integer transferAmount; - - /** - * 转账备注 - */ - @SerializedName("transfer_remark") - private String transferRemark; - - /** - * 用户在直连商户应用下的用户标示 - */ - @SerializedName("openid") - private String openid; - - /** - * 收款用户姓名 - */ - @SpecEncrypt - @SerializedName("user_name") - private String userName; - } -}