# Conflicts:
#	yudao-dependencies/pom.xml
#	yudao-framework/yudao-spring-boot-starter-web/src/main/java/cn/iocoder/yudao/framework/web/core/util/WebFrameworkUtils.java
#	yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java
This commit is contained in:
YunaiV
2025-05-06 20:50:33 +08:00
38 changed files with 697 additions and 81 deletions

View File

@@ -32,6 +32,10 @@ public class PayOrderRespDTO {
private String merchantOrderId;
// ========== 订单相关字段 ==========
/**
* 商品标题
*/
private String subject;
/**
* 支付金额,单位:分
*/
@@ -50,4 +54,15 @@ public class PayOrderRespDTO {
// ========== 渠道相关字段 ==========
/**
* 渠道用户编号
*
* 例如说,微信 openid、支付宝账号
*/
private String channelUserId;
/**
* 渠道订单号
*/
private String channelOrderNo;
}

View File

@@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.pay.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 支付渠道的编码的枚举
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum PayChannelEnum {
WX_PUB("wx_pub", "微信 JSAPI 支付"), // 公众号网页
WX_LITE("wx_lite", "微信小程序支付"),
WX_APP("wx_app", "微信 App 支付"),
WX_NATIVE("wx_native", "微信 Native 支付"),
WX_WAP("wx_wap", "微信 Wap 网站支付"), // H5 网页
WX_BAR("wx_bar", "微信付款码支付"),
ALIPAY_PC("alipay_pc", "支付宝 PC 网站支付"),
ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付"),
ALIPAY_APP("alipay_app", "支付宝App 支付"),
ALIPAY_QR("alipay_qr", "支付宝扫码支付"),
ALIPAY_BAR("alipay_bar", "支付宝条码支付"),
MOCK("mock", "模拟支付"),
WALLET("wallet", "钱包支付");
/**
* 编码
*
* 参考 <a href="https://www.pingxx.com/api/支付渠道属性值.html">支付渠道属性值</a>
*/
private final String code;
/**
* 名字
*/
private final String name;
}

View File

@@ -66,7 +66,8 @@ public class PayNotifyController {
@TenantIgnore
public String notifyOrder(@PathVariable("channelId") Long channelId,
@RequestParam(required = false) Map<String, String> params,
@RequestBody(required = false) String body) {
@RequestBody(required = false) String body,
@RequestHeader Map<String, String> headers) {
log.info("[notifyOrder][channelId({}) 回调数据({}/{})]", channelId, params, body);
// 1. 校验支付渠道是否存在
PayClient payClient = channelService.getPayClient(channelId);
@@ -76,7 +77,7 @@ public class PayNotifyController {
}
// 2. 解析通知数据
PayOrderRespDTO notify = payClient.parseOrderNotify(params, body);
PayOrderRespDTO notify = payClient.parseOrderNotify(params, body, headers);
orderService.notifyOrder(channelId, notify);
return "success";
}
@@ -87,7 +88,8 @@ public class PayNotifyController {
@TenantIgnore
public String notifyRefund(@PathVariable("channelId") Long channelId,
@RequestParam(required = false) Map<String, String> params,
@RequestBody(required = false) String body) {
@RequestBody(required = false) String body,
@RequestHeader Map<String, String> headers) {
log.info("[notifyRefund][channelId({}) 回调数据({}/{})]", channelId, params, body);
// 1. 校验支付渠道是否存在
PayClient payClient = channelService.getPayClient(channelId);
@@ -97,7 +99,7 @@ public class PayNotifyController {
}
// 2. 解析通知数据
PayRefundRespDTO notify = payClient.parseRefundNotify(params, body);
PayRefundRespDTO notify = payClient.parseRefundNotify(params, body, headers);
refundService.notifyRefund(channelId, notify);
return "success";
}
@@ -108,7 +110,8 @@ public class PayNotifyController {
@TenantIgnore
public String notifyTransfer(@PathVariable("channelId") Long channelId,
@RequestParam(required = false) Map<String, String> params,
@RequestBody(required = false) String body) {
@RequestBody(required = false) String body,
@RequestHeader Map<String, String> headers) {
log.info("[notifyTransfer][channelId({}) 回调数据({}/{})]", channelId, params, body);
// 1. 校验支付渠道是否存在
PayClient payClient = channelService.getPayClient(channelId);
@@ -118,7 +121,7 @@ public class PayNotifyController {
}
// 2. 解析通知数据
PayTransferRespDTO notify = payClient.parseTransferNotify(params, body);
PayTransferRespDTO notify = payClient.parseTransferNotify(params, body, headers);
payTransferService.notifyTransfer(channelId, notify);
return "success";
}

View File

@@ -89,7 +89,7 @@ public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
}
@Override
protected PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body) {
protected PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body, Map<String, String> headers) {
throw new UnsupportedOperationException("钱包支付无支付回调");
}
@@ -144,7 +144,7 @@ public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
}
@Override
protected PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) {
protected PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body, Map<String, String> headers) {
throw new UnsupportedOperationException("钱包支付无退款回调");
}
@@ -178,7 +178,7 @@ public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
}
@Override
protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body) throws Throwable {
protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) {
throw new UnsupportedOperationException("未实现");
}

View File

@@ -39,9 +39,10 @@ public interface PayClient {
*
* @param params HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数
* @param body HTTP 回调接口的 request body
* @param headers HTTP 回调接口的 request headers
* @return 支付订单信息
*/
PayOrderRespDTO parseOrderNotify(Map<String, String> params, String body);
PayOrderRespDTO parseOrderNotify(Map<String, String> params, String body, Map<String, String> headers);
/**
* 获得支付订单信息
@@ -66,9 +67,10 @@ public interface PayClient {
*
* @param params HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数
* @param body HTTP 回调接口的 request body
* @param headers HTTP 回调接口的 request headers
* @return 支付订单信息
*/
PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body);
PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body, Map<String, String> headers);
/**
* 获得退款订单信息
@@ -103,8 +105,9 @@ public interface PayClient {
*
* @param params HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数
* @param body HTTP 回调接口的 request body
* @param headers HTTP 回调接口的 request headers
* @return 转账信息
*/
PayTransferRespDTO parseTransferNotify(Map<String, String> params, String body);
PayTransferRespDTO parseTransferNotify(Map<String, String> params, String body, Map<String, String> headers);
}

View File

@@ -1,12 +1,8 @@
package cn.iocoder.yudao.framework.pay.core.client;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import java.util.Set;
import jakarta.validation.Validator;
/**
* 支付客户端的配置,本质是支付渠道的配置
@@ -18,6 +14,7 @@ import java.util.Set;
// @JsonTypeInfo 注解的作用Jackson 多态
// 1. 序列化到时数据库时,增加 @class 属性。
// 2. 反序列化到内存对象时,通过 @class 属性,可以创建出正确的类型
@JsonIgnoreProperties(ignoreUnknown = true) // 目的:忽略未知的属性,避免反序列化失败
public interface PayClientConfig {
/**

View File

@@ -101,19 +101,19 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
throws Throwable;
@Override
public final PayOrderRespDTO parseOrderNotify(Map<String, String> params, String body) {
public final PayOrderRespDTO parseOrderNotify(Map<String, String> params, String body, Map<String, String> headers) {
try {
return doParseOrderNotify(params, body);
return doParseOrderNotify(params, body, headers);
} catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可
throw ex;
} catch (Throwable ex) {
log.error("[parseOrderNotify][客户端({}) params({}) body({}) 解析失败]",
getId(), params, body, ex);
log.error("[parseOrderNotify][客户端({}) params({}) body({}) headers({}) 解析失败]",
getId(), params, body, headers, ex);
throw buildPayException(ex);
}
}
protected abstract PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body)
protected abstract PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body, Map<String, String> headers)
throws Throwable;
@Override
@@ -155,19 +155,19 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
protected abstract PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
@Override
public final PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body) {
public final PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body, Map<String, String> headers) {
try {
return doParseRefundNotify(params, body);
return doParseRefundNotify(params, body, headers);
} catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可
throw ex;
} catch (Throwable ex) {
log.error("[parseRefundNotify][客户端({}) params({}) body({}) 解析失败]",
getId(), params, body, ex);
log.error("[parseRefundNotify][客户端({}) params({}) body({}) headers({}) 解析失败]",
getId(), params, body, headers, ex);
throw buildPayException(ex);
}
}
protected abstract PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body)
protected abstract PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body, Map<String, String> headers)
throws Throwable;
@Override
@@ -220,19 +220,19 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
}
@Override
public final PayTransferRespDTO parseTransferNotify(Map<String, String> params, String body) {
public final PayTransferRespDTO parseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) {
try {
return doParseTransferNotify(params, body);
return doParseTransferNotify(params, body, headers);
} catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可
throw ex;
} catch (Throwable ex) {
log.error("[doParseTransferNotify][客户端({}) params({}) body({}) 解析失败]",
getId(), params, body, ex);
log.error("[doParseTransferNotify][客户端({}) params({}) body({}) headers({}) 解析失败]",
getId(), params, body, headers, ex);
throw buildPayException(ex);
}
}
protected abstract PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body)
protected abstract PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers)
throws Throwable;
@Override

View File

@@ -23,6 +23,8 @@ import com.alipay.api.AlipayResponse;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.*;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.internal.util.AntCertificationUtil;
import com.alipay.api.internal.util.codec.Base64;
import com.alipay.api.request.*;
import com.alipay.api.response.*;
import lombok.Getter;
@@ -30,6 +32,7 @@ import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Map;
@@ -41,6 +44,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
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;
/**
* 支付宝抽象类,实现支付宝统一的接口、以及部分实现(退款)
@@ -79,11 +83,23 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
}
@Override
public PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body) throws Throwable {
public PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body, Map<String, String> headers) throws Throwable {
// 1. 校验回调数据
Map<String, String> bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8);
AlipaySignature.rsaCheckV1(bodyObj, config.getAlipayPublicKey(),
StandardCharsets.UTF_8.name(), config.getSignType());
boolean verify;
if (Objects.equals(config.getMode(), MODE_PUBLIC_KEY)) {
verify = AlipaySignature.rsaCheckV1(params, config.getAlipayPublicKey(),
StandardCharsets.UTF_8.name(), config.getSignType());
} else if (Objects.equals(config.getMode(), MODE_CERTIFICATE)) {
// 由于 rsaCertCheckV1 的第二个参数是 path所以不能这么调用通过阅读源码发现可以采用如下方式
X509Certificate cert = AntCertificationUtil.getCertFromContent(config.getAlipayPublicCertContent());
String publicKey = Base64.encodeBase64String(cert.getEncoded());
verify = AlipaySignature.rsaCheckV1(bodyObj, publicKey,
StandardCharsets.UTF_8.name(), config.getSignType());
} else {
throw new IllegalArgumentException("未知的公钥类型:" + config.getMode());
}
Assert.isTrue(verify, "验签结果不通过");
// 2. 解析订单的状态
// 额外说明:支付宝不仅仅支付成功会回调,再各种触发支付单数据变化时,都会进行回调,所以这里 status 的解析会写的比较复杂
@@ -175,7 +191,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
}
@Override
public PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) {
public PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body, Map<String, String> headers) {
// 补充说明:支付宝退款时,没有回调,这点和微信支付是不同的。并且,退款分成部分退款、和全部退款。
// ① 部分退款:是会有回调,但是它回调的是订单状态的同步回调,不是退款订单的回调
// ② 全部退款Wap 支付有订单状态的同步回调,但是 PC/扫码又没有
@@ -327,7 +343,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
// TODO @chihuo这里是不是也要实现支付宝的。
@Override
protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body) throws Throwable {
protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) {
throw new UnsupportedOperationException("未实现");
}

View File

@@ -58,17 +58,17 @@ public class MockPayClient extends AbstractPayClient<NonePayClientConfig> {
}
@Override
protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body) throws Throwable {
protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) {
throw new UnsupportedOperationException("未实现");
}
@Override
protected PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) {
protected PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body, Map<String, String> headers) {
throw new UnsupportedOperationException("模拟支付无退款回调");
}
@Override
protected PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body) {
protected PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body, Map<String, String> headers) {
throw new UnsupportedOperationException("模拟支付无支付回调");
}

View File

@@ -18,10 +18,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.WxPayTransferPart
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.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
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;
@@ -67,13 +64,14 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
protected void doInit(String tradeType) {
// 创建 config 配置
WxPayConfig payConfig = new WxPayConfig();
BeanUtil.copyProperties(config, payConfig, "keyContent", "privateKeyContent");
BeanUtil.copyProperties(config, payConfig, "keyContent", "privateKeyContent", "publicKeyContent");
payConfig.setTradeType(tradeType);
// weixin-pay-java 无法设置内容,只允许读取文件,所以这里要创建临时文件来解决
if (Objects.equals(config.getApiVersion(), API_VERSION_V2)) {
payConfig.setKeyPath(FileUtils.createTempFile(Base64.decode(config.getKeyContent())).getPath());
} else if (Objects.equals(config.getApiVersion(), API_VERSION_V3)) {
payConfig.setPrivateKeyPath(FileUtils.createTempFile(config.getPrivateKeyContent()).getPath());
payConfig.setPublicKeyPath(FileUtils.createTempFile(config.getPublicKeyContent()).getPath());
}
// 创建 client 客户端
@@ -157,12 +155,12 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
}
@Override
public PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body) throws WxPayException {
public PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body, Map<String, String> headers) throws WxPayException {
switch (config.getApiVersion()) {
case API_VERSION_V2:
return doParseOrderNotifyV2(body);
case API_VERSION_V3:
return doParseOrderNotifyV3(body);
return doParseOrderNotifyV3(body, headers);
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
}
@@ -179,9 +177,11 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
response.getOutTradeNo(), body);
}
private PayOrderRespDTO doParseOrderNotifyV3(String body) throws WxPayException {
private PayOrderRespDTO doParseOrderNotifyV3(String body, Map<String, String> headers) throws WxPayException {
// 1. 解析回调
WxPayNotifyV3Result response = client.parseOrderNotifyV3Result(body, null);
// SignatureHeader signatureHeader = getRequestHeader(headers);
SignatureHeader signatureHeader = null;
WxPayNotifyV3Result response = client.parseOrderNotifyV3Result(body, signatureHeader);
WxPayNotifyV3Result.DecryptNotifyResult result = response.getResult();
// 2. 构建结果
Integer status = parseStatus(result.getTradeState());
@@ -321,12 +321,12 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
}
@Override
public PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) throws WxPayException {
public PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body, Map<String, String> headers) throws WxPayException {
switch (config.getApiVersion()) {
case API_VERSION_V2:
return doParseRefundNotifyV2(body);
case API_VERSION_V3:
return parseRefundNotifyV3(body);
return parseRefundNotifyV3(body, headers);
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
}
@@ -344,9 +344,10 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response);
}
private PayRefundRespDTO parseRefundNotifyV3(String body) throws WxPayException {
private PayRefundRespDTO parseRefundNotifyV3(String body, Map<String, String> headers) throws WxPayException {
// 1. 解析回调
WxPayRefundNotifyV3Result response = client.parseRefundNotifyV3Result(body, null);
SignatureHeader signatureHeader = getRequestHeader(headers);
WxPayRefundNotifyV3Result response = client.parseRefundNotifyV3Result(body, signatureHeader);
WxPayRefundNotifyV3Result.DecryptNotifyResult result = response.getResult();
// 2. 构建结果
if (Objects.equals("SUCCESS", result.getRefundStatus())) {
@@ -357,10 +358,10 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
}
@Override
public PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body) throws WxPayException {
public PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) throws WxPayException {
switch (config.getApiVersion()) {
case API_VERSION_V3:
return parseTransferNotifyV3(body);
return parseTransferNotifyV3(body, headers);
case API_VERSION_V2:
throw new UnsupportedOperationException("V2 版本暂不支持,建议使用 V3 版本");
default:
@@ -368,10 +369,11 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
}
}
private PayTransferRespDTO parseTransferNotifyV3(String body) throws WxPayException {
private PayTransferRespDTO parseTransferNotifyV3(String body, Map<String, String> headers) throws WxPayException {
// 1. 解析回调
SignatureHeader signatureHeader = getRequestHeader(headers);
// TODO @luchi这个可以复用 wxjava 里的类么?
WxPayTransferPartnerNotifyV3Result response = client.baseParseOrderNotifyV3Result(body, null, WxPayTransferPartnerNotifyV3Result.class, WxPayTransferPartnerNotifyV3Result.TransferNotifyResult.class);
WxPayTransferPartnerNotifyV3Result response = client.baseParseOrderNotifyV3Result(body, signatureHeader, WxPayTransferPartnerNotifyV3Result.class, WxPayTransferPartnerNotifyV3Result.TransferNotifyResult.class);
WxPayTransferPartnerNotifyV3Result.TransferNotifyResult result = response.getResult();
// 2. 构建结果
if (Objects.equals("FINISHED", result.getBatchStatus())) {
@@ -513,6 +515,20 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
// ========== 各种工具方法 ==========
/**
* 组装请求头重的签名信息
*
* @see <a href="https://github.com/binarywang/weixin-java-pay-demo/blob/master/src/main/java/com/github/binarywang/demo/wx/pay/controller/WxPayV3Controller.java#L202-L221">官方示例</a>
*/
private SignatureHeader getRequestHeader(Map<String, String> headers) {
return SignatureHeader.builder()
.signature(headers.get("wechatpay-signature"))
.nonce(headers.get("wechatpay-nonce"))
.serial(headers.get("wechatpay-serial"))
.timeStamp(headers.get("wechatpay-timestamp"))
.build();
}
static String formatDateV2(LocalDateTime time) {
return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), PURE_DATETIME_PATTERN);
}

View File

@@ -74,13 +74,18 @@ public class WxPayClientConfig implements PayClientConfig {
@NotBlank(message = "apiV3 密钥值不能为空", groups = V3.class)
private String apiV3Key;
/**
* 证书序列号
* 证书序列号merchantSerialNumber
*/
@NotBlank(message = "证书序列号不能为空", groups = V3.class)
private String certSerialNo;
@Deprecated // TODO 芋艿V2.3.0 进行移除
private String privateCertContent;
/**
* pub_key.pem 证书文件的对应字符串
*/
@NotBlank(message = "pub_key.pem 不能为空", groups = V3.class)
private String publicKeyContent;
@NotBlank(message = "publicKeyId 不能为空", groups = V3.class)
private String publicKeyId;
/**
* 分组校验 v2版本

View File

@@ -60,7 +60,7 @@ public class PayClientFactoryImplIntegrationTest {
config.setMchId("1545083881");
config.setApiVersion(WxPayClientConfig.API_VERSION_V3);
config.setPrivateKeyContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_key.pem")));
config.setPrivateCertContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_cert.pem")));
// config.setPrivateCertContent(IoUtil.readUtf8(new FileInputStream("/Users/yunai/Downloads/wx_pay/apiclient_cert.pem")));
config.setApiV3Key("joerVi8y5DJ3o4ttA0o1uH47Xz1u2Ase");
// 创建客户端
Long channelId = RandomUtil.randomLong();