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

feat:【PAY 支付】钱包支持转账功能
This commit is contained in:
YunaiV 2025-05-08 23:31:23 +08:00
parent f81dc105a2
commit 38c76806a3
17 changed files with 249 additions and 205 deletions

View File

@ -113,7 +113,7 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
if (BrokerageWithdrawTypeEnum.WALLET.getType().equals(withdraw.getType())) { if (BrokerageWithdrawTypeEnum.WALLET.getType().equals(withdraw.getType())) {
payWalletApi.addWalletBalance(new PayWalletAddBalanceReqDTO() payWalletApi.addWalletBalance(new PayWalletAddBalanceReqDTO()
.setUserId(withdraw.getUserId()).setUserType(UserTypeEnum.MEMBER.getValue()) .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())); .setPrice(withdraw.getPrice()));
// 1.2 微信 API // 1.2 微信 API
} else if (BrokerageWithdrawTypeEnum.WECHAT_API.getType().equals(withdraw.getType())) { } else if (BrokerageWithdrawTypeEnum.WECHAT_API.getType().equals(withdraw.getType())) {

View File

@ -1,10 +1,15 @@
package cn.iocoder.yudao.module.pay.api.transfer.dto; 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.Min;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@ -70,4 +75,46 @@ public class PayTransferCreateReqDTO {
*/ */
private String userName; private String userName;
/**
* 微信现金营销场景
*
* @param activityName 活动名称
* @param rewardDescription 奖励说明
* @return channelExtras
*/
public static Map<String, String> buildWeiXinChannelExtra1000(String activityName, String rewardDescription) {
return buildWeiXinChannelExtra(1000,
"活动名称", activityName,
"奖励说明", rewardDescription);
}
/**
* 微信企业报销场景
*
* @param expenseType 报销类型
* @param expenseDescription 报销说明
* @return channelExtras
*/
public static Map<String, String> buildWeiXinChannelExtra1006(String expenseType, String expenseDescription) {
return buildWeiXinChannelExtra(1006,
"报销类型", expenseType,
"报销说明", expenseDescription);
}
private static Map<String, String> buildWeiXinChannelExtra(Integer sceneId, String... values) {
Map<String, String> channelExtras = new HashMap<>();
// 构建场景报备信息列表
List<Map<String, String>> sceneReportInfos = new ArrayList<>();
for (int i = 0; i < values.length; i += 2) {
Map<String, String> 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;
}
} }

View File

@ -20,7 +20,7 @@ public enum PayWalletBizTypeEnum implements ArrayValuable<Integer> {
PAYMENT(3, "支付"), PAYMENT(3, "支付"),
PAYMENT_REFUND(4, "支付退款"), PAYMENT_REFUND(4, "支付退款"),
UPDATE_BALANCE(5, "更新余额"), UPDATE_BALANCE(5, "更新余额"),
BROKERAGE_WITHDRAW(6, "分佣提现"); TRANSFER(6, "转账");
/** /**
* 业务分类 * 业务分类

View File

@ -1,4 +1,4 @@
### 请求 /pay/pay/demo-order 接口 => 成功 ### 请求 /pay/pay/demo-order 接口(支付宝) => 成功
POST {{baseUrl}}/pay/demo-withdraw/create POST {{baseUrl}}/pay/demo-withdraw/create
Authorization: Bearer {{token}} Authorization: Bearer {{token}}
Content-Type: application/json Content-Type: application/json
@ -11,3 +11,30 @@ tenant-id: {{adminTenantId}}
"userAccount": "oespxk7368@sandbox.com", "userAccount": "oespxk7368@sandbox.com",
"userName": "oespxk7368" "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"
}

View File

@ -27,7 +27,6 @@ public class PayDemoWithdrawCreateReqVO {
private String userAccount; private String userAccount;
@Schema(description = "收款人姓名", example = "test1") @Schema(description = "收款人姓名", example = "test1")
@NotBlank(message = "收款人姓名不能为空")
private String userName; private String userName;
@Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @Schema(description = "提现方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")

View File

@ -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.app.PayAppDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO; 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.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.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient; 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.app.PayAppService;
import cn.iocoder.yudao.module.pay.service.order.PayOrderService; 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 com.google.common.collect.Maps;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
@ -51,6 +53,8 @@ public class PayOrderController {
private PayOrderService orderService; private PayOrderService orderService;
@Resource @Resource
private PayAppService appService; private PayAppService appService;
@Resource
private PayWalletService payWalletService;
@GetMapping("/get") @GetMapping("/get")
@Operation(summary = "获得支付订单") @Operation(summary = "获得支付订单")
@ -92,11 +96,11 @@ public class PayOrderController {
public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) { public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
// 1. 钱包支付事需要额外传 user_id user_type // 1. 钱包支付事需要额外传 user_id user_type
if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) { if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) {
Map<String, String> channelExtras = reqVO.getChannelExtras() == null ? if (reqVO.getChannelExtras() == null) {
Maps.newHashMapWithExpectedSize(2) : reqVO.getChannelExtras(); reqVO.setChannelExtras(Maps.newHashMapWithExpectedSize(1));
channelExtras.put(WalletPayClient.USER_ID_KEY, String.valueOf(getLoginUserId())); }
channelExtras.put(WalletPayClient.USER_TYPE_KEY, String.valueOf(getLoginUserType())); PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), getLoginUserType());
reqVO.setChannelExtras(channelExtras); reqVO.getChannelExtras().put(WalletPayClient.WALLET_ID_KEY, String.valueOf(wallet.getId()));
} }
// 2. 提交支付 // 2. 提交支付

View File

@ -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.controller.app.order.vo.AppPayOrderSubmitRespVO;
import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert; 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.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.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient; 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.order.PayOrderService;
import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; 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.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@ -39,6 +40,8 @@ public class AppPayOrderController {
@Resource @Resource
private PayOrderService payOrderService; private PayOrderService payOrderService;
@Resource
private PayWalletService payWalletService;
@GetMapping("/get") @GetMapping("/get")
@Operation(summary = "获得支付订单") @Operation(summary = "获得支付订单")
@ -63,11 +66,11 @@ public class AppPayOrderController {
public CommonResult<AppPayOrderSubmitRespVO> submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) { public CommonResult<AppPayOrderSubmitRespVO> submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) {
// 1. 钱包支付事需要额外传 user_id user_type // 1. 钱包支付事需要额外传 user_id user_type
if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) { if (Objects.equals(reqVO.getChannelCode(), PayChannelEnum.WALLET.getCode())) {
Map<String, String> channelExtras = reqVO.getChannelExtras() == null ? if (reqVO.getChannelExtras() == null) {
Maps.newHashMapWithExpectedSize(2) : reqVO.getChannelExtras(); reqVO.setChannelExtras(Maps.newHashMapWithExpectedSize(1));
channelExtras.put(WalletPayClient.USER_ID_KEY, String.valueOf(getLoginUserId())); }
channelExtras.put(WalletPayClient.USER_TYPE_KEY, String.valueOf(getLoginUserType())); PayWalletDO wallet = payWalletService.getOrCreateWallet(getLoginUserId(), getLoginUserType());
reqVO.setChannelExtras(channelExtras); reqVO.getChannelExtras().put(WalletPayClient.WALLET_ID_KEY, String.valueOf(wallet.getId()));
} }
// 2. 提交支付 // 2. 提交支付

View File

@ -53,6 +53,10 @@ public interface PayTransferMapper extends BaseMapperX<PayTransferDO> {
PayTransferDO::getNo, no); PayTransferDO::getNo, no);
} }
default PayTransferDO selectByNo(String no) {
return selectOne(PayTransferDO::getNo, no);
}
} }

View File

@ -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.client.impl.NonePayClientConfig;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum; 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.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.order.PayOrderExtensionDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO; 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.dal.dataobject.wallet.PayWalletTransactionDO;
import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; 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.order.PayOrderService;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService; 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.PayWalletService;
import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService; import cn.iocoder.yudao.module.pay.service.wallet.PayWalletTransactionService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -39,13 +42,14 @@ import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.REFUND_NOT_FO
@Slf4j @Slf4j
public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> { public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
public static final String USER_ID_KEY = "user_id"; public static final String WALLET_ID_KEY = "walletId";
public static final String USER_TYPE_KEY = "user_type";
private PayWalletService wallService; private PayWalletService wallService;
private PayWalletTransactionService walletTransactionService; private PayWalletTransactionService walletTransactionService;
private PayOrderService orderService; private PayOrderService orderService;
private PayRefundService refundService; private PayRefundService refundService;
private PayTransferService transferService;
public WalletPayClient(Long channelId, NonePayClientConfig config) { public WalletPayClient(Long channelId, NonePayClientConfig config) {
super(channelId, PayChannelEnum.WALLET.getCode(), config); super(channelId, PayChannelEnum.WALLET.getCode(), config);
@ -62,19 +66,18 @@ public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
} }
@Override @Override
@SuppressWarnings("PatternVariableCanBeUsed")
protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
try { try {
Long userId = MapUtil.getLong(reqDTO.getChannelExtras(), USER_ID_KEY); Long walletId = MapUtil.getLong(reqDTO.getChannelExtras(), WALLET_ID_KEY);
Integer userType = MapUtil.getInt(reqDTO.getChannelExtras(), USER_TYPE_KEY); Assert.notNull(walletId, "钱包编号");
Assert.notNull(userId, "用户 id 不能为空"); PayWalletTransactionDO transaction = wallService.orderPay(walletId,
Assert.notNull(userType, "用户类型不能为空"); reqDTO.getOutTradeNo(), reqDTO.getPrice());
PayWalletTransactionDO transaction = wallService.orderPay(userId, userType, reqDTO.getOutTradeNo(),
reqDTO.getPrice());
return PayOrderRespDTO.successOf(transaction.getNo(), transaction.getCreator(), return PayOrderRespDTO.successOf(transaction.getNo(), transaction.getCreator(),
transaction.getCreateTime(), transaction.getCreateTime(),
reqDTO.getOutTradeNo(), transaction); reqDTO.getOutTradeNo(), transaction);
} catch (Throwable ex) { } catch (Throwable ex) {
log.error("[doUnifiedOrder] 失败", ex); log.error("[doUnifiedOrder][reqDTO({}) 异常]", reqDTO, ex);
Integer errorCode = INTERNAL_SERVER_ERROR.getCode(); Integer errorCode = INTERNAL_SERVER_ERROR.getCode();
String errorMsg = INTERNAL_SERVER_ERROR.getMsg(); String errorMsg = INTERNAL_SERVER_ERROR.getMsg();
if (ex instanceof ServiceException) { if (ex instanceof ServiceException) {
@ -122,6 +125,7 @@ public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
} }
@Override @Override
@SuppressWarnings("PatternVariableCanBeUsed")
protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) { protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
try { try {
PayWalletTransactionDO payWalletTransaction = wallService.orderRefund(reqDTO.getOutRefundNo(), PayWalletTransactionDO payWalletTransaction = wallService.orderRefund(reqDTO.getOutRefundNo(),
@ -129,7 +133,7 @@ public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
return PayRefundRespDTO.successOf(payWalletTransaction.getNo(), payWalletTransaction.getCreateTime(), return PayRefundRespDTO.successOf(payWalletTransaction.getNo(), payWalletTransaction.getCreateTime(),
reqDTO.getOutRefundNo(), payWalletTransaction); reqDTO.getOutRefundNo(), payWalletTransaction);
} catch (Throwable ex) { } catch (Throwable ex) {
log.error("[doUnifiedRefund] 失败", ex); log.error("[doUnifiedRefund][reqDOT({}) 异常]", reqDTO, ex);
Integer errorCode = INTERNAL_SERVER_ERROR.getCode(); Integer errorCode = INTERNAL_SERVER_ERROR.getCode();
String errorMsg = INTERNAL_SERVER_ERROR.getMsg(); String errorMsg = INTERNAL_SERVER_ERROR.getMsg();
if (ex instanceof ServiceException) { if (ex instanceof ServiceException) {
@ -177,18 +181,71 @@ public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
} }
@Override @Override
protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) { @SuppressWarnings("PatternVariableCanBeUsed")
throw new UnsupportedOperationException("未实现"); 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 @Override
public PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) { protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) {
throw new UnsupportedOperationException("待实现"); throw new UnsupportedOperationException("钱包支付无转账回调");
} }
@Override @Override
protected PayTransferRespDTO doGetTransfer(String outTradeNo) { 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));
} }
} }

View File

@ -57,11 +57,16 @@ public class PayDemoTransferServiceImpl implements PayDemoWithdrawService {
demoTransferMapper.insert(withdraw); demoTransferMapper.insert(withdraw);
// 2.1 创建支付单 // 2.1 创建支付单
Long payTransferId = payTransferApi.createTransfer(new PayTransferCreateReqDTO() PayTransferCreateReqDTO transferReqDTO = new PayTransferCreateReqDTO()
.setAppKey(PAY_APP_KEY).setChannelCode(withdraw.getTransferChannelCode()).setUserIp(getClientIP()) // 支付应用 .setAppKey(PAY_APP_KEY).setChannelCode(withdraw.getTransferChannelCode()).setUserIp(getClientIP()) // 支付应用
.setMerchantOrderId(String.valueOf(withdraw.getId())) // 业务的订单编号 .setMerchantOrderId(String.valueOf(withdraw.getId())) // 业务的订单编号
.setSubject(reqVO.getSubject()).setPrice(withdraw.getPrice()) // 价格信息 .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 示例提现单 // 2.2 更新转账单到 demo 示例提现单
demoTransferMapper.updateById(new PayDemoWithdrawDO().setId(withdraw.getId()) demoTransferMapper.updateById(new PayDemoWithdrawDO().setId(withdraw.getId())
.setPayTransferId(payTransferId)); .setPayTransferId(payTransferId));

View File

@ -28,6 +28,14 @@ public interface PayTransferService {
*/ */
PayTransferDO getTransfer(Long id); PayTransferDO getTransfer(Long id);
/**
* 根据转账单号获取转账单
*
* @param no 转账单号
* @return 转账单
*/
PayTransferDO getTransferByNo(String no);
/** /**
* 获得转账单分页 * 获得转账单分页
* *

View File

@ -238,6 +238,11 @@ public class PayTransferServiceImpl implements PayTransferService {
return transferMapper.selectById(id); return transferMapper.selectById(id);
} }
@Override
public PayTransferDO getTransferByNo(String no) {
return transferMapper.selectByNo(no);
}
@Override @Override
public PageResult<PayTransferDO> getTransferPage(PayTransferPageReqVO pageReqVO) { public PageResult<PayTransferDO> getTransferPage(PayTransferPageReqVO pageReqVO) {
return transferMapper.selectPage(pageReqVO); return transferMapper.selectPage(pageReqVO);

View File

@ -41,12 +41,11 @@ public interface PayWalletService {
/** /**
* 钱包订单支付 * 钱包订单支付
* *
* @param userId 用户 id * @param walletId 钱包编号
* @param userType 用户类型
* @param outTradeNo 外部订单号 * @param outTradeNo 外部订单号
* @param price 金额 * @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 walletId 钱包编号
* @param bizId 业务关联 id * @param bizId 业务关联编号
* @param bizType 业务关联分类 * @param bizType 业务关联分类
* @param price 扣减金额 * @param price 扣减金额
* @return 钱包流水 * @return 钱包流水
@ -72,8 +71,8 @@ public interface PayWalletService {
/** /**
* 增加钱包余额 * 增加钱包余额
* *
* @param walletId 钱包 id * @param walletId 钱包编号
* @param bizId 业务关联 id * @param bizId 业务关联编号
* @param bizType 业务关联分类 * @param bizType 业务关联分类
* @param price 增加金额 * @param price 增加金额
* @return 钱包流水 * @return 钱包流水

View File

@ -81,13 +81,13 @@ public class PayWalletServiceImpl implements PayWalletService {
@Override @Override
@Transactional(rollbackFor = Exception.class) @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. 判断支付交易拓展单是否存 // 1. 判断支付交易拓展单是否存
PayOrderExtensionDO orderExtension = orderService.getOrderExtensionByNo(outTradeNo); PayOrderExtensionDO orderExtension = orderService.getOrderExtensionByNo(outTradeNo);
if (orderExtension == null) { if (orderExtension == null) {
throw exception(PAY_ORDER_EXTENSION_NOT_FOUND); throw exception(PAY_ORDER_EXTENSION_NOT_FOUND);
} }
PayWalletDO wallet = getOrCreateWallet(userId, userType); PayWalletDO wallet = walletMapper.selectById(walletId);
// 2. 扣减余额 // 2. 扣减余额
return reduceWalletBalance(wallet.getId(), orderExtension.getOrderId(), PAYMENT, price); return reduceWalletBalance(wallet.getId(), orderExtension.getOrderId(), PAYMENT, price);
} }
@ -198,7 +198,7 @@ public class PayWalletServiceImpl implements PayWalletService {
break; break;
} }
case UPDATE_BALANCE: // 更新余额 case UPDATE_BALANCE: // 更新余额
case BROKERAGE_WITHDRAW: // 分佣提现 case TRANSFER: // 分佣提现
walletMapper.updateWhenAdd(payWallet.getId(), price); walletMapper.updateWhenAdd(payWallet.getId(), price);
break; break;
default: { default: {

View File

@ -58,6 +58,8 @@ public class PayTransferUnifiedReqDTO {
/** /**
* 支付渠道的额外参数 * 支付渠道的额外参数
*
* 微信支付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">按转账场景报备背景信息</>
*/ */
private Map<String, String> channelExtras; private Map<String, String> channelExtras;

View File

@ -7,6 +7,7 @@ import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.date.TemporalAccessorUtil; import cn.hutool.core.date.TemporalAccessorUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.io.FileUtils; 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.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.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO; 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.notify.*;
import com.github.binarywang.wxpay.bean.request.*; import com.github.binarywang.wxpay.bean.request.*;
import com.github.binarywang.wxpay.bean.result.*; import com.github.binarywang.wxpay.bean.result.*;
import com.github.binarywang.wxpay.bean.transfer.QueryTransferBatchesRequest; import com.github.binarywang.wxpay.bean.transfer.TransferBillsGetResult;
import com.github.binarywang.wxpay.bean.transfer.QueryTransferBatchesResult; import com.github.binarywang.wxpay.bean.transfer.TransferBillsRequest;
import com.github.binarywang.wxpay.bean.transfer.TransferBatchesRequest; import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
import com.github.binarywang.wxpay.bean.transfer.TransferBatchesResult;
import com.github.binarywang.wxpay.config.WxPayConfig; import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.exception.WxPayException; import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService; import com.github.binarywang.wxpay.service.WxPayService;
@ -32,8 +32,6 @@ import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
import java.util.Collections;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -463,53 +461,57 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
@Override @Override
protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws WxPayException { protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws WxPayException {
// 1. 构建 TransferBatchesRequest 请求 // 1. 构建 TransferBillsRequest 请求
List<TransferBatchesRequest.TransferDetail> transferDetailList = Collections.singletonList( TransferBillsRequest request = TransferBillsRequest.newBuilder()
TransferBatchesRequest.TransferDetail.newBuilder() .appid(this.config.getAppId())
.outDetailNo(reqDTO.getOutTransferNo()) .outBillNo(reqDTO.getOutTransferNo())
.transferAmount(reqDTO.getPrice()) .transferAmount(reqDTO.getPrice())
.transferRemark(reqDTO.getSubject()) .transferRemark(reqDTO.getSubject())
.transferSceneId(reqDTO.getChannelExtras().get("sceneId"))
.openid(reqDTO.getUserAccount()) .openid(reqDTO.getUserAccount())
.build()); .userName(reqDTO.getUserName())
// TODO @luchi能不能我们搞个 TransferBatchesRequestX extends TransferBatchesRequest这样更简洁一点 .transferSceneReportInfos(JsonUtils.parseArray(reqDTO.getChannelExtras().get("sceneReportInfos"),
TransferBatchesRequest transferBatches = TransferBatchesRequest.newBuilder() TransferBillsRequest.TransferSceneReportInfo.class))
.appid(this.config.getAppId()) .notifyUrl(reqDTO.getNotifyUrl())
.outBatchNo(reqDTO.getOutTransferNo()) .build();
.batchName(reqDTO.getSubject()) // 特殊微信转账必须 0.3 元起才允许传入姓名
.batchRemark(reqDTO.getSubject()) if (reqDTO.getPrice() < 30) {
.totalAmount(reqDTO.getPrice()) request.setUserName(null);
.totalNum(transferDetailList.size()) }
.transferDetailList(transferDetailList).build()
.setNotifyUrl(reqDTO.getNotifyUrl());
// 2.1 执行请求 // 2.1 执行请求
TransferBatchesResult transferBatchesResult = client.getTransferService().transferBatches(transferBatches); try {
TransferBillsResult response = client.getTransferService().transferBills(request);
System.out.println(response);
// 2.2 创建返回结果 // 2.2 创建返回结果
return PayTransferRespDTO.processingOf(transferBatchesResult.getBatchId(), reqDTO.getOutTransferNo(), transferBatchesResult); // 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 @Override
protected PayTransferRespDTO doGetTransfer(String outTradeNo) throws WxPayException { protected PayTransferRespDTO doGetTransfer(String outTradeNo) throws WxPayException {
QueryTransferBatchesRequest request = QueryTransferBatchesRequest.newBuilder() // 1. 执行请求
.outBatchNo(outTradeNo).needQueryDetail(true).offset(0).limit(20).detailStatus("ALL") TransferBillsGetResult response = client.getTransferService().getBillsByTransferBillNo(outTradeNo);
.build();
QueryTransferBatchesResult response = client.getTransferService().transferBatchesOutBatchNo(request); // 2. 创建返回结果
QueryTransferBatchesResult.TransferBatch transferBatch = response.getTransferBatch(); String state = response.getState();
if (Objects.equals("FINISHED", transferBatch.getBatchStatus())) { if (ObjectUtils.equalsAny(state, "ACCEPTED", "PROCESSING", "WAIT_USER_CONFIRM", "TRANSFERING")) {
// 明细中全部成功则成功任一失败则失败 return PayTransferRespDTO.processingOf(response.getTransferBillNo(), response.getOutBillNo(), response);
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()))) { if (Objects.equals("SUCCESS", state)) {
return PayTransferRespDTO.closedOf(transferBatch.getBatchStatus(), transferBatch.getCloseReason(), return PayTransferRespDTO.successOf(response.getTransferBillNo(), parseDateV3(response.getUpdateTime()),
transferBatch.getOutBatchNo(), response); response.getOutBillNo(), response);
} }
} return PayTransferRespDTO.closedOf(state, response.getFailReason(),
if (Objects.equals("CLOSED", transferBatch.getBatchStatus())) { response.getOutBillNo(), response);
return PayTransferRespDTO.closedOf(transferBatch.getBatchStatus(), transferBatch.getCloseReason(),
transferBatch.getOutBatchNo(), response);
}
return PayTransferRespDTO.processingOf(transferBatch.getBatchId(), transferBatch.getOutBatchNo(), response);
} }
// ========== 各种工具方法 ========== // ========== 各种工具方法 ==========

View File

@ -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<TransferDetail> 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;
}
}