pay:同步最新功能的代码(钱包、转账)

This commit is contained in:
YunaiV
2023-10-24 08:00:01 +08:00
parent 9fc31ac2ae
commit df2b1b45a4
122 changed files with 5161 additions and 455 deletions

View File

@@ -4,6 +4,8 @@ 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.refund.PayRefundRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
import java.util.Map;
@@ -76,4 +78,12 @@ public interface PayClient {
*/
PayRefundRespDTO getRefund(String outTradeNo, String outRefundNo);
/**
* 调用渠道,进行转账
*
* @param reqDTO 统一转账请求信息
* @return 转账信息
*/
PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO);
}

View File

@@ -1,5 +1,7 @@
package cn.iocoder.yudao.framework.pay.core.client;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
/**
* 支付客户端的工厂接口
*
@@ -25,4 +27,12 @@ public interface PayClientFactory {
<Config extends PayClientConfig> void createOrUpdatePayClient(Long channelId, String channelCode,
Config config);
/**
* 注册支付客户端 Class用于模块中实现的 PayClient
*
* @param channel 支付渠道的编码的枚举
* @param payClientClass 支付客户端 class
*/
void registerPayClientClass(PayChannelEnum channel, Class<?> payClientClass);
}

View File

@@ -0,0 +1,96 @@
package cn.iocoder.yudao.framework.pay.core.client.dto.transfer;
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 统一转账 Response DTO
*
* @author jason
*/
@Data
public class PayTransferRespDTO {
/**
* 转账状态
*
* 关联 {@link PayTransferStatusRespEnum#getStatus()}
*/
private Integer status;
/**
* 外部转账单号
*
*/
private String outTransferNo;
/**
* 支付渠道编号
*/
private String channelOrderNo;
/**
* 支付成功时间
*/
private LocalDateTime successTime;
/**
* 原始的返回结果
*/
private Object rawData;
/**
* 调用渠道的错误码
*/
private String channelErrorCode;
/**
* 调用渠道报错时,错误信息
*/
private String channelErrorMsg;
/**
* 创建【WAITING】状态的转账返回
*/
public static PayTransferRespDTO waitingOf(String channelOrderNo,
String outTransferNo, Object rawData) {
PayTransferRespDTO respDTO = new PayTransferRespDTO();
respDTO.status = PayTransferStatusRespEnum.WAITING.getStatus();
respDTO.channelOrderNo = channelOrderNo;
respDTO.outTransferNo = outTransferNo;
respDTO.rawData = rawData;
return respDTO;
}
/**
* 创建【CLOSED】状态的转账返回
*/
public static PayTransferRespDTO closedOf(String channelErrorCode, String channelErrorMsg,
String outTransferNo, Object rawData) {
PayTransferRespDTO respDTO = new PayTransferRespDTO();
respDTO.status = PayTransferStatusRespEnum.CLOSED.getStatus();
respDTO.channelErrorCode = channelErrorCode;
respDTO.channelErrorMsg = channelErrorMsg;
// 相对通用的字段
respDTO.outTransferNo = outTransferNo;
respDTO.rawData = rawData;
return respDTO;
}
/**
* 创建【SUCCESS】状态的转账返回
*/
public static PayTransferRespDTO successOf(String channelTransferNo, LocalDateTime successTime,
String outTransferNo, Object rawData) {
PayTransferRespDTO respDTO = new PayTransferRespDTO();
respDTO.status = PayTransferStatusRespEnum.SUCCESS.getStatus();
respDTO.channelOrderNo = channelTransferNo;
respDTO.successTime = successTime;
// 相对通用的字段
respDTO.outTransferNo = outTransferNo;
respDTO.rawData = rawData;
return respDTO;
}
}

View File

@@ -0,0 +1,66 @@
package cn.iocoder.yudao.framework.pay.core.client.dto.transfer;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Map;
/**
* 统一转账 Request DTO
*
* @author jason
*/
@Data
public class PayTransferUnifiedReqDTO {
/**
* 转账类型
*
* 关联 {@link PayTransferTypeEnum#getType()}
*/
@NotNull(message = "转账类型不能为空")
@InEnum(PayTransferTypeEnum.class)
private Integer type;
/**
* 用户 IP
*/
@NotEmpty(message = "用户 IP 不能为空")
private String userIp;
@NotEmpty(message = "外部转账单编号不能为空")
private String outTransferNo;
/**
* 转账金额,单位:分
*/
@NotNull(message = "转账金额不能为空")
@Min(value = 1, message = "转账金额必须大于零")
private Integer price;
/**
* 转账标题
*/
@NotEmpty(message = "转账标题不能为空")
@Length(max = 128, message = "转账标题不能超过 128")
private String title;
/**
* 收款方信息。
*
* 转账类型 {@link #type} 不同,收款方信息不同
*/
@NotEmpty(message = "收款方信息 不能为空")
private Map<String, String> payeeInfo;
/**
* 支付渠道的额外参数
*/
private Map<String, String> channelExtras;
}

View File

@@ -8,6 +8,8 @@ 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.refund.PayRefundRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
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 lombok.extern.slf4j.Slf4j;
@@ -181,6 +183,26 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
protected abstract PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo)
throws Throwable;
@Override
public final PayTransferRespDTO unifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
ValidationUtils.validate(reqDTO);
PayTransferRespDTO resp;
try{
resp = doUnifiedTransfer(reqDTO);
}catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可
throw ex;
} catch (Throwable ex) {
// 系统异常,则包装成 PayException 异常抛出
log.error("[unifiedTransfer][客户端({}) request({}) 发起转账异常]",
getId(), toJsonString(reqDTO), ex);
throw buildPayException(ex);
}
return resp;
}
protected abstract PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO)
throws Throwable;
// ========== 各种工具方法 ==========
private PayException buildPayException(Throwable ex) {

View File

@@ -0,0 +1,31 @@
package cn.iocoder.yudao.framework.pay.core.client.impl;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import lombok.Data;
import javax.validation.Validator;
/**
* 无需任何配置 PayClientConfig 实现类
*
* @author jason
*/
@Data
public class NonePayClientConfig implements PayClientConfig {
/**
* 配置名称
* <p>
* 如果不加任何属性JsonUtils.parseObject2 解析会报错,所以暂时加个名称
*/
private String name;
public NonePayClientConfig(){
this.name = "none-config";
}
@Override
public void validate(Validator validator) {
// 无任何配置不需要校验
}
}

View File

@@ -1,19 +1,22 @@
package cn.iocoder.yudao.framework.pay.core.client.impl;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ReflectUtil;
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.*;
import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.*;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import static cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum.*;
/**
* 支付客户端的工厂实现类
*
@@ -24,10 +27,38 @@ public class PayClientFactoryImpl implements PayClientFactory {
/**
* 支付客户端 Map
*
* key渠道编号
*/
private final ConcurrentMap<Long, AbstractPayClient<?>> clients = new ConcurrentHashMap<>();
/**
* 支付客户端 Class Map
*/
private final Map<PayChannelEnum, Class<?>> clientClass = new ConcurrentHashMap<>();
public PayClientFactoryImpl() {
// 微信支付客户端
clientClass.put(WX_PUB, WxPubPayClient.class);
clientClass.put(WX_LITE, WxLitePayClient.class);
clientClass.put(WX_APP, WxAppPayClient.class);
clientClass.put(WX_BAR, WxBarPayClient.class);
clientClass.put(WX_NATIVE, WxNativePayClient.class);
// 支付包支付客户端
clientClass.put(ALIPAY_WAP, AlipayWapPayClient.class);
clientClass.put(ALIPAY_QR, AlipayQrPayClient.class);
clientClass.put(ALIPAY_APP, AlipayAppPayClient.class);
clientClass.put(ALIPAY_PC, AlipayPcPayClient.class);
clientClass.put(ALIPAY_BAR, AlipayBarPayClient.class);
// Mock 支付客户端
clientClass.put(MOCK, MockPayClient.class);
}
@Override
public void registerPayClientClass(PayChannelEnum channel, Class<?> payClientClass) {
clientClass.put(channel, payClientClass);
}
@Override
public PayClient getPayClient(Long channelId) {
AbstractPayClient<?> client = clients.get(channelId);
@@ -52,30 +83,13 @@ public class PayClientFactoryImpl implements PayClientFactory {
}
@SuppressWarnings("unchecked")
private <Config extends PayClientConfig> AbstractPayClient<Config> createPayClient(
Long channelId, String channelCode, Config config) {
private <Config extends PayClientConfig> AbstractPayClient<Config> createPayClient(Long channelId, String channelCode,
Config config) {
PayChannelEnum channelEnum = PayChannelEnum.getByCode(channelCode);
Assert.notNull(channelEnum, String.format("支付渠道(%s) 为空", channelEnum));
// 创建客户端
switch (channelEnum) {
// 微信支付
case WX_PUB: return (AbstractPayClient<Config>) new WxPubPayClient(channelId, (WxPayClientConfig) config);
case WX_LITE: return (AbstractPayClient<Config>) new WxLitePayClient(channelId, (WxPayClientConfig) config);
case WX_APP: return (AbstractPayClient<Config>) new WxAppPayClient(channelId, (WxPayClientConfig) config);
case WX_BAR: return (AbstractPayClient<Config>) new WxBarPayClient(channelId, (WxPayClientConfig) config);
case WX_NATIVE: return (AbstractPayClient<Config>) new WxNativePayClient(channelId, (WxPayClientConfig) config);
// 支付宝支付
case ALIPAY_WAP: return (AbstractPayClient<Config>) new AlipayWapPayClient(channelId, (AlipayPayClientConfig) config);
case ALIPAY_QR: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
case ALIPAY_APP: return (AbstractPayClient<Config>) new AlipayAppPayClient(channelId, (AlipayPayClientConfig) config);
case ALIPAY_PC: return (AbstractPayClient<Config>) new AlipayPcPayClient(channelId, (AlipayPayClientConfig) config);
case ALIPAY_BAR: return (AbstractPayClient<Config>) new AlipayBarPayClient(channelId, (AlipayPayClientConfig) config);
// 其它支付
case MOCK: return (AbstractPayClient<Config>) new MockPayClient(channelId, (MockPayClientConfig) config);
}
// 创建失败,错误日志 + 抛出异常
log.error("[createPayClient][配置({}) 找不到合适的客户端实现]", config);
throw new IllegalArgumentException(String.format("配置(%s) 找不到合适的客户端实现", config));
Assert.notNull(channelEnum, String.format("支付渠道(%s) 为空", channelCode));
Class<?> payClientClass = clientClass.get(channelEnum);
Assert.notNull(payClientClass, String.format("支付渠道(%s) Class 为空", channelCode));
return (AbstractPayClient<Config>) ReflectUtil.newInstance(payClientClass, channelId, config);
}
}

View File

@@ -6,27 +6,32 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
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;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
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.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayConfig;
import com.alipay.api.AlipayResponse;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeFastpayRefundQueryModel;
import com.alipay.api.domain.AlipayTradeQueryModel;
import com.alipay.api.domain.AlipayTradeRefundModel;
import com.alipay.api.domain.*;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayFundTransUniTransferRequest;
import com.alipay.api.request.AlipayTradeFastpayRefundQueryRequest;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayFundTransUniTransferResponse;
import com.alipay.api.response.AlipayTradeFastpayRefundQueryResponse;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.alipay.api.response.AlipayTradeRefundResponse;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@@ -38,6 +43,9 @@ 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.BAD_REQUEST;
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;
/**
* 支付宝抽象类,实现支付宝统一的接口、以及部分实现(退款)
@@ -47,6 +55,7 @@ import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
@Slf4j
public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPayClientConfig> {
@Getter // 仅用于单测场景
protected DefaultAlipayClient client;
public AbstractAlipayPayClient(Long channelId, String channelCode, AlipayPayClientConfig config) {
@@ -103,16 +112,20 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
// 1.2 构建 AlipayTradeQueryRequest 请求
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
request.setBizModel(model);
// 2.1 执行请求
AlipayTradeQueryResponse response = client.execute(request);
AlipayTradeQueryResponse response;
if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) {
// 证书模式
response = client.certificateExecute(request);
} else {
response = client.execute(request);
}
if (!response.isSuccess()) { // 不成功,例如说订单不存在
return PayOrderRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
outTradeNo, response);
}
// 2.2 解析订单的状态
Integer status = parseStatus(response.getTradeStatus());
Assert.notNull(status, (Supplier<Throwable>) () -> {
Assert.notNull(status, () -> {
throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", response.getBody()));
});
return PayOrderRespDTO.of(status, response.getTradeNo(), response.getBuyerUserId(), LocalDateTimeUtil.of(response.getSendPayDate()),
@@ -146,9 +159,18 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
request.setBizModel(model);
// 2.1 执行请求
AlipayTradeRefundResponse response = client.execute(request);
AlipayTradeRefundResponse response;
if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { // 证书模式
response = client.certificateExecute(request);
} else {
response = client.execute(request);
}
if (!response.isSuccess()) {
return PayRefundRespDTO.failureOf(reqDTO.getOutRefundNo(), response);
// 当出现 ACQ.SYSTEM_ERROR, 退款可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询
if (ObjectUtils.equalsAny(response.getSubCode(), "ACQ.SYSTEM_ERROR", "SYSTEM_ERROR")) {
return PayRefundRespDTO.waitingOf(null, reqDTO.getOutRefundNo(), response);
}
return PayRefundRespDTO.failureOf(response.getSubCode(), response.getSubMsg(), reqDTO.getOutRefundNo(), response);
}
// 2.2 创建返回结果
// 支付宝只要退款调用返回 success就认为退款成功不需要回调。具体可见 parseNotify 方法的说明。
@@ -179,7 +201,12 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
request.setBizModel(model);
// 2.1 执行请求
AlipayTradeFastpayRefundQueryResponse response = client.execute(request);
AlipayTradeFastpayRefundQueryResponse response;
if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) { // 证书模式
response = client.certificateExecute(request);
} else {
response = client.execute(request);
}
if (!response.isSuccess()) {
// 明确不存在的情况,应该就是失败,可进行关闭
if (ObjectUtils.equalsAny(response.getSubCode(), "TRADE_NOT_EXIST", "ACQ.TRADE_NOT_EXIST")) {
@@ -196,6 +223,70 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
return PayRefundRespDTO.waitingOf(null, outRefundNo, response);
}
@Override
protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws AlipayApiException {
// 1.1 校验公钥类型 必须使用公钥证书模式
if (!Objects.equals(config.getMode(), MODE_CERTIFICATE)) {
throw new IllegalStateException("支付宝单笔转账必须使用公钥证书模式");
}
// 1.2 构建 AlipayFundTransUniTransferModel
AlipayFundTransUniTransferModel model = new AlipayFundTransUniTransferModel();
// ① 通用的参数
model.setTransAmount(formatAmount(reqDTO.getPrice())); // 转账金额
model.setOrderTitle(reqDTO.getTitle()); // 转账业务的标题,用于在支付宝用户的账单里显示。
model.setOutBizNo(reqDTO.getOutTransferNo());
model.setProductCode("TRANS_ACCOUNT_NO_PWD"); // 销售产品码。单笔无密转账固定为 TRANS_ACCOUNT_NO_PWD
model.setBizScene("DIRECT_TRANSFER"); // 业务场景 单笔无密转账固定为 DIRECT_TRANSFER
model.setBusinessParams(JsonUtils.toJsonString(reqDTO.getChannelExtras()));
PayTransferTypeEnum transferType = PayTransferTypeEnum.typeOf(reqDTO.getType());
switch (transferType) {
// TODO @jason是不是不用传递 transferType 参数哈?因为应该已经明确是支付宝啦?
// @芋艿。 是不是还要考虑转账到银行卡。所以传 transferType 但是转账到银行卡不知道要如何测试??
case ALIPAY_BALANCE: {
// ② 个性化的参数
Participant payeeInfo = new Participant();
payeeInfo.setIdentityType("ALIPAY_LOGON_ID");
String logonId = MapUtil.getStr(reqDTO.getPayeeInfo(), "ALIPAY_LOGON_ID");
if (StrUtil.isEmpty(logonId)) {
throw exception0(BAD_REQUEST.getCode(), "支付包登录 ID 不能为空");
}
String accountName = MapUtil.getStr(reqDTO.getPayeeInfo(), "ALIPAY_ACCOUNT_NAME");
if (StrUtil.isEmpty(accountName)) {
throw exception0(BAD_REQUEST.getCode(), "支付包账户名称不能为空");
}
payeeInfo.setIdentity(logonId); // 支付宝登录号
payeeInfo.setName(accountName); // 支付宝账号姓名
model.setPayeeInfo(payeeInfo);
// 1.3 构建 AlipayFundTransUniTransferRequest
AlipayFundTransUniTransferRequest request = new AlipayFundTransUniTransferRequest();
request.setBizModel(model);
// 执行请求
AlipayFundTransUniTransferResponse response = client.certificateExecute(request);
// 处理结果
if (!response.isSuccess()) {
// 当出现 SYSTEM_ERROR, 转账可能成功也可能失败。 返回 WAIT 状态. 后续 job 会轮询
if (ObjectUtils.equalsAny(response.getSubCode(), "SYSTEM_ERROR", "ACQ.SYSTEM_ERROR")) {
return PayTransferRespDTO.waitingOf(null, reqDTO.getOutTransferNo(), response);
}
return PayTransferRespDTO.closedOf(response.getSubCode(), response.getSubMsg(),
reqDTO.getOutTransferNo(), response);
}
return PayTransferRespDTO.successOf(response.getOrderId(), parseTime(response.getTransDate()),
response.getOutBizNo(), response);
}
case BANK_CARD: {
Participant payeeInfo = new Participant();
payeeInfo.setIdentityType("BANKCARD_ACCOUNT");
// TODO 待实现
throw new UnsupportedOperationException("待实现");
}
default: {
throw new IllegalStateException("不正确的转账类型: " + transferType);
}
}
}
// ========== 各种工具方法 ==========
protected String formatAmount(Integer amount) {

View File

@@ -56,5 +56,4 @@ public class AlipayAppPayClient extends AbstractAlipayPayClient {
return PayOrderRespDTO.waitingOf(displayMode, response.getBody(),
reqDTO.getOutTradeNo(), response);
}
}

View File

@@ -13,8 +13,12 @@ import com.alipay.api.request.AlipayTradePayRequest;
import com.alipay.api.response.AlipayTradePayResponse;
import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
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;
/**
* 支付宝【条码支付】的 PayClient 实现类
@@ -57,18 +61,25 @@ public class AlipayBarPayClient extends AbstractAlipayPayClient {
request.setReturnUrl(reqDTO.getReturnUrl());
// 2.1 执行请求
AlipayTradePayResponse response = client.execute(request);
AlipayTradePayResponse response;
if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) {
// 证书模式
response = client.certificateExecute(request);
} else {
response = client.execute(request);
}
// 2.2 处理结果
if (!response.isSuccess()) {
return buildClosedPayOrderRespDTO(reqDTO, response);
}
if ("10000".equals(response.getCode())) { // 免密支付
return PayOrderRespDTO.successOf(response.getTradeNo(), response.getBuyerUserId(), LocalDateTimeUtil.of(response.getGmtPayment()),
response.getOutTradeNo(), response);
LocalDateTime successTime = LocalDateTimeUtil.of(response.getGmtPayment());
return PayOrderRespDTO.successOf(response.getTradeNo(), response.getBuyerUserId(), successTime,
response.getOutTradeNo(), response)
.setDisplayMode(displayMode).setDisplayContent("");
}
// 大额支付,需要用户输入密码,所以返回 waiting。此时前端一般会进行轮询
return PayOrderRespDTO.waitingOf(displayMode, "",
reqDTO.getOutTradeNo(), response);
}
}

View File

@@ -66,5 +66,4 @@ public class AlipayPcPayClient extends AbstractAlipayPayClient {
return PayOrderRespDTO.waitingOf(displayMode, response.getBody(),
reqDTO.getOutTradeNo(), response);
}
}

View File

@@ -10,6 +10,10 @@ import com.alipay.api.request.AlipayTradePrecreateRequest;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import lombok.extern.slf4j.Slf4j;
import java.util.Objects;
import static cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig.MODE_CERTIFICATE;
/**
* 支付宝【扫码支付】的 PayClient 实现类
*
@@ -45,7 +49,13 @@ public class AlipayQrPayClient extends AbstractAlipayPayClient {
request.setReturnUrl(reqDTO.getReturnUrl());
// 2.1 执行请求
AlipayTradePrecreateResponse response = client.execute(request);
AlipayTradePrecreateResponse response;
if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) {
// 证书模式
response = client.certificateExecute(request);
} else {
response = client.execute(request);
}
// 2.2 处理结果
if (!response.isSuccess()) {
return buildClosedPayOrderRespDTO(reqDTO, response);
@@ -53,5 +63,4 @@ public class AlipayQrPayClient extends AbstractAlipayPayClient {
return PayOrderRespDTO.waitingOf(displayMode, response.getQrCode(),
reqDTO.getOutTradeNo(), response);
}
}

View File

@@ -55,5 +55,4 @@ public class AlipayWapPayClient extends AbstractAlipayPayClient {
return PayOrderRespDTO.waitingOf(displayMode, response.getBody(),
reqDTO.getOutTradeNo(), response);
}
}

View File

@@ -4,7 +4,10 @@ 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.refund.PayRefundRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
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.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import java.time.LocalDateTime;
@@ -17,11 +20,11 @@ import java.util.Map;
*
* @author jason
*/
public class MockPayClient extends AbstractPayClient<MockPayClientConfig> {
public class MockPayClient extends AbstractPayClient<NonePayClientConfig> {
private static final String MOCK_RESP_SUCCESS_DATA = "MOCK_SUCCESS";
public MockPayClient(Long channelId, MockPayClientConfig config) {
public MockPayClient(Long channelId, NonePayClientConfig config) {
super(channelId, PayChannelEnum.MOCK.getCode(), config);
}
@@ -63,4 +66,9 @@ public class MockPayClient extends AbstractPayClient<MockPayClientConfig> {
throw new UnsupportedOperationException("模拟支付无支付回调");
}
@Override
protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
throw new UnsupportedOperationException("待实现");
}
}

View File

@@ -12,6 +12,8 @@ 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.refund.PayRefundRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
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.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
@@ -425,6 +427,10 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
}
}
@Override
protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
throw new UnsupportedOperationException("待实现");
}
// ========== 各种工具方法 ==========
static String formatDateV2(LocalDateTime time) {

View File

@@ -2,8 +2,8 @@ package cn.iocoder.yudao.framework.pay.core.enums.channel;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.mock.MockPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -28,8 +28,9 @@ public enum PayChannelEnum {
ALIPAY_APP("alipay_app", "支付宝App 支付", AlipayPayClientConfig.class),
ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class),
ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class),
MOCK("mock", "模拟支付", NonePayClientConfig.class),
MOCK("mock", "模拟支付", MockPayClientConfig.class);
WALLET("wallet", "钱包支付", NonePayClientConfig.class);
/**
* 编码

View File

@@ -0,0 +1,41 @@
package cn.iocoder.yudao.framework.pay.core.enums.transfer;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Objects;
/**
* 渠道的转账状态枚举
*
* @author jason
*/
@Getter
@AllArgsConstructor
public enum PayTransferStatusRespEnum {
WAITING(0, "转账中"),
/**
* TODO 转账到银行卡. 会有T+0 T+1 到账的请情况。 还未实现
* TODO @jason可以看看其它开源项目针对这个场景处理策略是怎么样的例如说每天主动轮询这个状态的单子
*/
IN_PROGRESS(10, "转账进行中"),
SUCCESS(20, "转账成功"),
/**
* 转账关闭 (失败,或者其它情况)
*/
CLOSED(30, "转账关闭");
private final Integer status;
private final String name;
public static boolean isSuccess(Integer status) {
return Objects.equals(status, SUCCESS.getStatus());
}
public static boolean isClosed(Integer status) {
return Objects.equals(status, CLOSED.getStatus());
}
}

View File

@@ -0,0 +1,41 @@
package cn.iocoder.yudao.framework.pay.core.enums.transfer;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 转账类型枚举
*
* @author jason
*/
@AllArgsConstructor
@Getter
public enum PayTransferTypeEnum implements IntArrayValuable {
ALIPAY_BALANCE(1, "支付宝余额"),
WX_BALANCE(2, "微信余额"),
BANK_CARD(3, "银行卡"),
WALLET_BALANCE(4, "钱包余额");
public static final String ALIPAY_LOGON_ID = "ALIPAY_LOGON_ID";
public static final String ALIPAY_ACCOUNT_NAME = "ALIPAY_ACCOUNT_NAME";
private final Integer type;
private final String name;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PayTransferTypeEnum::getType).toArray();
@Override
public int[] array() {
return ARRAYS;
}
public static PayTransferTypeEnum typeOf(Integer type) {
return ArrayUtil.firstMatch(item -> item.getType().equals(type), values());
}
}