feat:【PAY 支付】完善支付转账示例,以及转账功能(支付宝)

This commit is contained in:
YunaiV
2025-05-07 23:37:24 +08:00
parent ffee189589
commit 1e7f22cde0
30 changed files with 251 additions and 605 deletions

View File

@@ -66,10 +66,10 @@ public class PayTransferRespDTO {
/**
* 创建【IN_PROGRESS】状态的转账返回
*/
public static PayTransferRespDTO dealingOf(String channelTransferNo,
String outTransferNo, Object rawData) {
public static PayTransferRespDTO processingOf(String channelTransferNo,
String outTransferNo, Object rawData) {
PayTransferRespDTO respDTO = new PayTransferRespDTO();
respDTO.status = PayTransferStatusRespEnum.IN_PROGRESS.getStatus();
respDTO.status = PayTransferStatusRespEnum.PROCESSING.getStatus();
respDTO.channelTransferNo = channelTransferNo;
respDTO.outTransferNo = outTransferNo;
respDTO.rawData = rawData;

View File

@@ -1,7 +1,6 @@
package cn.iocoder.yudao.framework.pay.core.client.dto.transfer;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@@ -10,9 +9,6 @@ import org.hibernate.validator.constraints.URL;
import java.util.Map;
import static cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum.Alipay;
import static cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum.WxPay;
/**
* 统一转账 Request DTO
*
@@ -27,6 +23,9 @@ public class PayTransferUnifiedReqDTO {
@NotEmpty(message = "用户 IP 不能为空")
private String userIp;
/**
* 外部转账单编号
*/
@NotEmpty(message = "外部转账单编号不能为空")
private String outTransferNo;
@@ -44,25 +43,19 @@ public class PayTransferUnifiedReqDTO {
@Length(max = 128, message = "转账标题不能超过 128")
private String subject;
// TODO @芋艿userName、alipayLogonId、openid =》channelExtras另外validatePayTransferReqDTO 去掉;
/**
* 收款人账号
*
* 微信场景下openid
* 支付宝场景下:支付宝账号
*/
@NotEmpty(message = "收款人账号不能为空")
private String userAccount;
/**
* 收款人姓名
*/
@NotBlank(message = "收款人姓名不能为空", groups = {Alipay.class})
private String userName;
/**
* 支付宝登录号
*/
@NotBlank(message = "支付宝登录号不能为空", groups = {Alipay.class})
private String alipayLogonId;
/**
* 微信 openId
*/
@NotBlank(message = "微信 openId 不能为空", groups = {WxPay.class})
private String openid;
/**
* 支付渠道的额外参数
*/

View File

@@ -11,13 +11,10 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReq
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.exception.PayException;
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
/**

View File

@@ -4,7 +4,6 @@ import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
@@ -40,8 +39,6 @@ import java.util.Objects;
import java.util.function.Supplier;
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.ERROR_CONFIGURATION;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
import static cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_CERTIFICATE;
import static cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_PUBLIC_KEY;
@@ -235,10 +232,9 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
@Override
protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws AlipayApiException {
// 0. 校验公钥类型:必须使用公钥证书模式
if (ObjectUtil.notEqual(config.getMode(), MODE_CERTIFICATE)) {
throw exception0(ERROR_CONFIGURATION.getCode(), "支付宝单笔转账必须使用公钥证书模式");
}
// 补充说明https://opendocs.alipay.com/open/03dcrm?pathHash=4ba3b20b
// 沙箱环境:可通过 公钥模式 或 公钥证书模式 加签进行调试
// 生产环境:必须使用 公钥证书模式 加签请求强校验请求
// 1.1 构建 AlipayFundTransUniTransferModel
AlipayFundTransUniTransferModel model = new AlipayFundTransUniTransferModel();
@@ -254,7 +250,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
// ② 个性化的参数
Participant payeeInfo = new Participant();
payeeInfo.setIdentityType("ALIPAY_LOGON_ID"); // 暂时只考虑转账到支付宝,银行没有权限 https://opendocs.alipay.com/open/02byvc?scene=66dd06f5a923403393b85de68d3c0055
payeeInfo.setIdentity(reqDTO.getAlipayLogonId()); // 支付宝登录号
payeeInfo.setIdentity(reqDTO.getUserAccount()); // 支付宝登录号
payeeInfo.setName(reqDTO.getUserName()); // 支付宝账号姓名
model.setPayeeInfo(payeeInfo);
// 1.2 构建 AlipayFundTransUniTransferRequest
@@ -262,7 +258,12 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
request.setBizModel(model);
// 2.1 执行请求
AlipayFundTransUniTransferResponse response = client.certificateExecute(request);
AlipayFundTransUniTransferResponse response;
if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { // 证书模式
response = client.certificateExecute(request);
} else {
response = client.execute(request);
}
if (!response.isSuccess()) {
// 当出现 SYSTEM_ERROR, 转账可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询,或相同 outBizNo 重新发起转账
// 发现 outBizNo 相同 两次请求参数相同. 会返回 "PAYMENT_INFO_INCONSISTENCY", 不知道哪里的问题. 暂时返回 WAIT. 后续job 会轮询
@@ -278,7 +279,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
reqDTO.getOutTransferNo(), response);
}
if (Objects.equals(response.getStatus(), "DEALING")) { // 转账到银行卡会出现 "DEALING" 处理中
return PayTransferRespDTO.dealingOf(response.getOrderId(), reqDTO.getOutTransferNo(), response);
return PayTransferRespDTO.processingOf(response.getOrderId(), reqDTO.getOutTransferNo(), response);
}
return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getTransDate()),
response.getOutBizNo(), response);
@@ -317,7 +318,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
outTradeNo, response);
}
if (Objects.equals(response.getStatus(), "DEALING")) { // 转账到银行卡会出现 "DEALING" 处理中
return PayTransferRespDTO.dealingOf(response.getOrderId(), outTradeNo, response);
return PayTransferRespDTO.processingOf(response.getOrderId(), outTradeNo, response);
}
return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getPayDate()),
response.getOutBizNo(), response);

View File

@@ -469,7 +469,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
.outDetailNo(reqDTO.getOutTransferNo())
.transferAmount(reqDTO.getPrice())
.transferRemark(reqDTO.getSubject())
.openid(reqDTO.getOpenid())
.openid(reqDTO.getUserAccount())
.build());
// TODO @luchi能不能我们搞个 TransferBatchesRequestX extends TransferBatchesRequest这样更简洁一点。
TransferBatchesRequest transferBatches = TransferBatchesRequest.newBuilder()
@@ -484,7 +484,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
// 2.1 执行请求
TransferBatchesResult transferBatchesResult = client.getTransferService().transferBatches(transferBatches);
// 2.2 创建返回结果
return PayTransferRespDTO.dealingOf(transferBatchesResult.getBatchId(), reqDTO.getOutTransferNo(), transferBatchesResult);
return PayTransferRespDTO.processingOf(transferBatchesResult.getBatchId(), reqDTO.getOutTransferNo(), transferBatchesResult);
}
@Override
@@ -509,7 +509,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
return PayTransferRespDTO.closedOf(transferBatch.getBatchStatus(), transferBatch.getCloseReason(),
transferBatch.getOutBatchNo(), response);
}
return PayTransferRespDTO.dealingOf(transferBatch.getBatchId(), transferBatch.getOutBatchNo(), response);
return PayTransferRespDTO.processingOf(transferBatch.getBatchId(), transferBatch.getOutBatchNo(), response);
}
// ========== 各种工具方法 ==========

View File

@@ -4,7 +4,6 @@ import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
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.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
@@ -28,10 +27,6 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString
@Slf4j
public class WxPubPayClient extends AbstractWxPayClient {
public WxPubPayClient(Long channelId, WxPayClientConfig config) {
super(channelId, PayChannelEnum.WX_PUB.getCode(), config);
}
protected WxPubPayClient(Long channelId, String channelCode, WxPayClientConfig config) {
super(channelId, channelCode, config);
}

View File

@@ -15,18 +15,9 @@ import java.util.Objects;
public enum PayTransferStatusRespEnum {
WAITING(0, "等待转账"),
/**
* TODO 转账到银行卡. 会有T+0 T+1 到账的请情况。 还未实现
* TODO @jason可以看看其它开源项目针对这个场景处理策略是怎么样的例如说每天主动轮询这个状态的单子
*/
IN_PROGRESS(10, "转账进行中"),
SUCCESS(20, "转账成功"),
/**
* 转账关闭 (失败,或者其它情况)
*/
CLOSED(30, "转账关闭");
PROCESSING(5, "转账进行中"),
SUCCESS(10, "转账成功"),
CLOSED(20, "转账关闭");
private final Integer status;
private final String name;
@@ -39,7 +30,8 @@ public enum PayTransferStatusRespEnum {
return Objects.equals(status, CLOSED.getStatus());
}
public static boolean isInProgress(Integer status) {
return Objects.equals(status, IN_PROGRESS.getStatus());
public static boolean isProcessing(Integer status) {
return Objects.equals(status, PROCESSING.getStatus());
}
}

View File

@@ -1,45 +0,0 @@
package cn.iocoder.yudao.framework.pay.core.enums.transfer;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
// TODO @芋艿:【转账】这里要不要改成支付平台?
/**
* 转账类型枚举
*
* @author jason
*/
@AllArgsConstructor
@Getter
public enum PayTransferTypeEnum implements ArrayValuable<Integer> {
ALIPAY_BALANCE(1, "支付宝余额"),
WX_BALANCE(2, "微信余额"),
BANK_CARD(3, "银行卡"),
WALLET_BALANCE(4, "钱包余额");
public interface WxPay {
}
public interface Alipay {
}
private final Integer type;
private final String name;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(PayTransferTypeEnum::getType).toArray(Integer[]::new);
@Override
public Integer[] array() {
return ARRAYS;
}
public static PayTransferTypeEnum typeOf(Integer type) {
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
}
}