🎨 #3968【微信支付】修复微信支付api-host-url配置反向代理路径前缀时会导致v3签名异常的问题
This commit is contained in:
@@ -23,6 +23,8 @@ wx:
|
|||||||
pay:
|
pay:
|
||||||
appId: xxxxxxxxxxx
|
appId: xxxxxxxxxxx
|
||||||
mchId: 15xxxxxxxxx #商户id
|
mchId: 15xxxxxxxxx #商户id
|
||||||
|
apiHostUrl: http://10.0.0.1:3128 # 可选:代理主机
|
||||||
|
apiHostUrlPath: /api-weixin # 可选:代理入口前缀
|
||||||
apiV3Key: Dc1DBwSc094jACxxxxxxxxxxxxxxx #V3密钥
|
apiV3Key: Dc1DBwSc094jACxxxxxxxxxxxxxxx #V3密钥
|
||||||
certSerialNo: 62C6CEAA360BCxxxxxxxxxxxxxxx
|
certSerialNo: 62C6CEAA360BCxxxxxxxxxxxxxxx
|
||||||
privateKeyPath: classpath:cert/apiclient_key.pem #apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径
|
privateKeyPath: classpath:cert/apiclient_key.pem #apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ public class WxPayAutoConfiguration {
|
|||||||
payConfig.setPublicKeyId(StringUtils.trimToNull(this.properties.getPublicKeyId()));
|
payConfig.setPublicKeyId(StringUtils.trimToNull(this.properties.getPublicKeyId()));
|
||||||
payConfig.setPublicKeyPath(StringUtils.trimToNull(this.properties.getPublicKeyPath()));
|
payConfig.setPublicKeyPath(StringUtils.trimToNull(this.properties.getPublicKeyPath()));
|
||||||
payConfig.setApiHostUrl(StringUtils.trimToNull(this.properties.getApiHostUrl()));
|
payConfig.setApiHostUrl(StringUtils.trimToNull(this.properties.getApiHostUrl()));
|
||||||
|
payConfig.setApiHostUrlPath(StringUtils.trimToNull(this.properties.getApiHostUrlPath()));
|
||||||
payConfig.setStrictlyNeedWechatPaySerial(this.properties.isStrictlyNeedWechatPaySerial());
|
payConfig.setStrictlyNeedWechatPaySerial(this.properties.isStrictlyNeedWechatPaySerial());
|
||||||
payConfig.setFullPublicKeyModel(this.properties.isFullPublicKeyModel());
|
payConfig.setFullPublicKeyModel(this.properties.isFullPublicKeyModel());
|
||||||
|
|
||||||
|
|||||||
@@ -113,6 +113,12 @@ public class WxPayProperties {
|
|||||||
*/
|
*/
|
||||||
private String apiHostUrl;
|
private String apiHostUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义API主机路径前缀(用于代理入口前缀)
|
||||||
|
* 例如:/api-weixin
|
||||||
|
*/
|
||||||
|
private String apiHostUrlPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认添加
|
* 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认添加
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -255,6 +255,7 @@ public class PayService {
|
|||||||
| payScorePermissionNotifyUrl | 支付分授权回调地址 | 无 |
|
| payScorePermissionNotifyUrl | 支付分授权回调地址 | 无 |
|
||||||
| useSandboxEnv | 是否使用沙箱环境 | false |
|
| useSandboxEnv | 是否使用沙箱环境 | false |
|
||||||
| apiHostUrl | 自定义API主机地址 | https://api.mch.weixin.qq.com |
|
| apiHostUrl | 自定义API主机地址 | https://api.mch.weixin.qq.com |
|
||||||
|
| apiHostUrlPath | 自定义API主机路径前缀(代理入口前缀) | 空 |
|
||||||
| strictlyNeedWechatPaySerial | 是否所有V3请求都添加序列号头 | true |
|
| strictlyNeedWechatPaySerial | 是否所有V3请求都添加序列号头 | true |
|
||||||
| fullPublicKeyModel | 是否完全使用公钥模式 | true |
|
| fullPublicKeyModel | 是否完全使用公钥模式 | true |
|
||||||
| publicKeyId | 公钥ID | 无 |
|
| publicKeyId | 公钥ID | 无 |
|
||||||
|
|||||||
@@ -112,6 +112,12 @@ public class WxPaySingleProperties implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private String apiHostUrl;
|
private String apiHostUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义API主机路径前缀(用于代理入口前缀).
|
||||||
|
* 例如:/api-weixin
|
||||||
|
*/
|
||||||
|
private String apiHostUrlPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认添加.
|
* 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认添加.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ public class WxPayMultiServicesImpl implements WxPayMultiServices {
|
|||||||
payConfig.setPublicKeyId(StringUtils.trimToNull(properties.getPublicKeyId()));
|
payConfig.setPublicKeyId(StringUtils.trimToNull(properties.getPublicKeyId()));
|
||||||
payConfig.setPublicKeyPath(StringUtils.trimToNull(properties.getPublicKeyPath()));
|
payConfig.setPublicKeyPath(StringUtils.trimToNull(properties.getPublicKeyPath()));
|
||||||
payConfig.setApiHostUrl(StringUtils.trimToNull(properties.getApiHostUrl()));
|
payConfig.setApiHostUrl(StringUtils.trimToNull(properties.getApiHostUrl()));
|
||||||
|
payConfig.setApiHostUrlPath(StringUtils.trimToNull(properties.getApiHostUrlPath()));
|
||||||
payConfig.setStrictlyNeedWechatPaySerial(properties.isStrictlyNeedWechatPaySerial());
|
payConfig.setStrictlyNeedWechatPaySerial(properties.isStrictlyNeedWechatPaySerial());
|
||||||
payConfig.setFullPublicKeyModel(properties.isFullPublicKeyModel());
|
payConfig.setFullPublicKeyModel(properties.isFullPublicKeyModel());
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||||||
"wx.pay.configs.app1.notify-url=https://example.com/pay/notify",
|
"wx.pay.configs.app1.notify-url=https://example.com/pay/notify",
|
||||||
"wx.pay.configs.app2.app-id=wx2222222222222222",
|
"wx.pay.configs.app2.app-id=wx2222222222222222",
|
||||||
"wx.pay.configs.app2.mch-id=2222222222",
|
"wx.pay.configs.app2.mch-id=2222222222",
|
||||||
|
"wx.pay.configs.app2.api-host-url=http://10.0.0.1:3128",
|
||||||
|
"wx.pay.configs.app2.api-host-url-path=/api-weixin",
|
||||||
"wx.pay.configs.app2.apiv3-key=22222222222222222222222222222222",
|
"wx.pay.configs.app2.apiv3-key=22222222222222222222222222222222",
|
||||||
"wx.pay.configs.app2.cert-serial-no=2222222222222222",
|
"wx.pay.configs.app2.cert-serial-no=2222222222222222",
|
||||||
"wx.pay.configs.app2.private-key-path=classpath:cert/apiclient_key.pem",
|
"wx.pay.configs.app2.private-key-path=classpath:cert/apiclient_key.pem",
|
||||||
@@ -57,7 +59,9 @@ public class WxPayMultiServicesTest {
|
|||||||
assertNotNull(app2Config, "app2 configuration should exist");
|
assertNotNull(app2Config, "app2 configuration should exist");
|
||||||
assertEquals("wx2222222222222222", app2Config.getAppId());
|
assertEquals("wx2222222222222222", app2Config.getAppId());
|
||||||
assertEquals("2222222222", app2Config.getMchId());
|
assertEquals("2222222222", app2Config.getMchId());
|
||||||
assertEquals("22222222222222222222222222222222", app2Config.getApiV3Key());
|
assertEquals("http://10.0.0.1:3128", app2Config.getApiHostUrl());
|
||||||
|
assertEquals("/api-weixin", app2Config.getApiHostUrlPath());
|
||||||
|
assertEquals("22222222222222222222222222222222", app2Config.getApiv3Key());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -71,6 +75,7 @@ public class WxPayMultiServicesTest {
|
|||||||
assertNotNull(app2Service, "Should get WxPayService for app2");
|
assertNotNull(app2Service, "Should get WxPayService for app2");
|
||||||
assertEquals("wx2222222222222222", app2Service.getConfig().getAppId());
|
assertEquals("wx2222222222222222", app2Service.getConfig().getAppId());
|
||||||
assertEquals("2222222222", app2Service.getConfig().getMchId());
|
assertEquals("2222222222", app2Service.getConfig().getMchId());
|
||||||
|
assertEquals("/api-weixin", app2Service.getConfig().getApiHostUrlPath());
|
||||||
|
|
||||||
// 测试相同key返回相同实例
|
// 测试相同key返回相同实例
|
||||||
WxPayService app1ServiceAgain = wxPayMultiServices.getWxPayService("app1");
|
WxPayService app1ServiceAgain = wxPayMultiServices.getWxPayService("app1");
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ wx:
|
|||||||
pay:
|
pay:
|
||||||
appId: xxxxxxxxxxx
|
appId: xxxxxxxxxxx
|
||||||
mchId: 15xxxxxxxxx #商户id
|
mchId: 15xxxxxxxxx #商户id
|
||||||
|
apiHostUrl: http://10.0.0.1:3128 # 可选:代理主机
|
||||||
|
apiHostUrlPath: /api-weixin # 可选:代理入口前缀
|
||||||
apiV3Key: Dc1DBwSc094jACxxxxxxxxxxxxxxx #V3密钥
|
apiV3Key: Dc1DBwSc094jACxxxxxxxxxxxxxxx #V3密钥
|
||||||
certSerialNo: 62C6CEAA360BCxxxxxxxxxxxxxxx
|
certSerialNo: 62C6CEAA360BCxxxxxxxxxxxxxxx
|
||||||
privateKeyPath: classpath:cert/apiclient_key.pem #apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径
|
privateKeyPath: classpath:cert/apiclient_key.pem #apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ public class WxPayAutoConfiguration {
|
|||||||
payConfig.setPublicKeyId(StringUtils.trimToNull(this.properties.getPublicKeyId()));
|
payConfig.setPublicKeyId(StringUtils.trimToNull(this.properties.getPublicKeyId()));
|
||||||
payConfig.setPublicKeyPath(StringUtils.trimToNull(this.properties.getPublicKeyPath()));
|
payConfig.setPublicKeyPath(StringUtils.trimToNull(this.properties.getPublicKeyPath()));
|
||||||
payConfig.setApiHostUrl(StringUtils.trimToNull(this.properties.getApiHostUrl()));
|
payConfig.setApiHostUrl(StringUtils.trimToNull(this.properties.getApiHostUrl()));
|
||||||
|
payConfig.setApiHostUrlPath(StringUtils.trimToNull(this.properties.getApiHostUrlPath()));
|
||||||
payConfig.setStrictlyNeedWechatPaySerial(this.properties.isStrictlyNeedWechatPaySerial());
|
payConfig.setStrictlyNeedWechatPaySerial(this.properties.isStrictlyNeedWechatPaySerial());
|
||||||
payConfig.setFullPublicKeyModel(this.properties.isFullPublicKeyModel());
|
payConfig.setFullPublicKeyModel(this.properties.isFullPublicKeyModel());
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,12 @@ public class WxPayProperties {
|
|||||||
*/
|
*/
|
||||||
private String apiHostUrl;
|
private String apiHostUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义API主机路径前缀(用于代理入口前缀)
|
||||||
|
* 例如:/api-weixin
|
||||||
|
*/
|
||||||
|
private String apiHostUrlPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认添加
|
* 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认添加
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import lombok.AccessLevel;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
@@ -118,8 +120,19 @@ class VerifierBuilder {
|
|||||||
String certSerialNo, String mchId, String apiV3Key, PrivateKey merchantPrivateKey,
|
String certSerialNo, String mchId, String apiV3Key, PrivateKey merchantPrivateKey,
|
||||||
WxPayHttpProxy wxPayHttpProxy, int certAutoUpdateTime, String payBaseUrl
|
WxPayHttpProxy wxPayHttpProxy, int certAutoUpdateTime, String payBaseUrl
|
||||||
) {
|
) {
|
||||||
|
String signUriStripPrefix = null;
|
||||||
|
if (StringUtils.isNotBlank(payBaseUrl)) {
|
||||||
|
try {
|
||||||
|
String rawPath = new URI(payBaseUrl).getRawPath();
|
||||||
|
if (StringUtils.isNotBlank(rawPath) && !"/".equals(rawPath)) {
|
||||||
|
signUriStripPrefix = rawPath;
|
||||||
|
}
|
||||||
|
} catch (URISyntaxException ignored) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
return new AutoUpdateCertificatesVerifier(
|
return new AutoUpdateCertificatesVerifier(
|
||||||
new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)),
|
new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey), signUriStripPrefix),
|
||||||
apiV3Key.getBytes(StandardCharsets.UTF_8), certAutoUpdateTime,
|
apiV3Key.getBytes(StandardCharsets.UTF_8), certAutoUpdateTime,
|
||||||
payBaseUrl, wxPayHttpProxy);
|
payBaseUrl, wxPayHttpProxy);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,12 @@ public class WxPayConfig {
|
|||||||
*/
|
*/
|
||||||
private String apiHostUrl = DEFAULT_PAY_BASE_URL;
|
private String apiHostUrl = DEFAULT_PAY_BASE_URL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付接口请求地址路径前缀(用于网关代理前缀).
|
||||||
|
* 例如:/api-weixin
|
||||||
|
*/
|
||||||
|
private String apiHostUrlPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* http请求连接超时时间.
|
* http请求连接超时时间.
|
||||||
*/
|
*/
|
||||||
@@ -285,11 +291,42 @@ public class WxPayConfig {
|
|||||||
* @return 微信支付接口请求地址域名
|
* @return 微信支付接口请求地址域名
|
||||||
*/
|
*/
|
||||||
public String getApiHostUrl() {
|
public String getApiHostUrl() {
|
||||||
if (StringUtils.isEmpty(this.apiHostUrl)) {
|
String hostUrl = StringUtils.trimToNull(this.apiHostUrl);
|
||||||
|
if (hostUrl == null) {
|
||||||
return DEFAULT_PAY_BASE_URL;
|
return DEFAULT_PAY_BASE_URL;
|
||||||
}
|
}
|
||||||
|
if (hostUrl.endsWith("/")) {
|
||||||
|
hostUrl = hostUrl.substring(0, hostUrl.length() - 1);
|
||||||
|
}
|
||||||
|
return hostUrl;
|
||||||
|
}
|
||||||
|
|
||||||
return this.apiHostUrl;
|
/**
|
||||||
|
* 返回所设置的微信支付接口路径前缀.
|
||||||
|
*
|
||||||
|
* @return 路径前缀,不配置时为空字符串
|
||||||
|
*/
|
||||||
|
public String getApiHostUrlPath() {
|
||||||
|
String pathPrefix = StringUtils.trimToNull(this.apiHostUrlPath);
|
||||||
|
if (pathPrefix == null || "/".equals(pathPrefix)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (!pathPrefix.startsWith("/")) {
|
||||||
|
pathPrefix = "/" + pathPrefix;
|
||||||
|
}
|
||||||
|
if (pathPrefix.endsWith("/")) {
|
||||||
|
pathPrefix = pathPrefix.substring(0, pathPrefix.length() - 1);
|
||||||
|
}
|
||||||
|
return pathPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回用于请求层拼接的基础地址:host + pathPrefix.
|
||||||
|
*
|
||||||
|
* @return 拼接后的基础地址
|
||||||
|
*/
|
||||||
|
public String getApiHostWithPathPrefix() {
|
||||||
|
return this.getApiHostUrl() + this.getApiHostUrlPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
@@ -391,10 +428,11 @@ public class WxPayConfig {
|
|||||||
} else {
|
} else {
|
||||||
certificatesVerifier = VerifierBuilder.build(
|
certificatesVerifier = VerifierBuilder.build(
|
||||||
this.getCertSerialNo(), this.getMchId(), this.getApiV3Key(), merchantPrivateKey, wxPayHttpProxy,
|
this.getCertSerialNo(), this.getMchId(), this.getApiV3Key(), merchantPrivateKey, wxPayHttpProxy,
|
||||||
this.getCertAutoUpdateTime(), this.getApiHostUrl(), this.getPublicKeyId(), publicKey);
|
this.getCertAutoUpdateTime(), this.getApiHostWithPathPrefix(), this.getPublicKeyId(), publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
WxPayV3HttpClientBuilder wxPayV3HttpClientBuilder = WxPayV3HttpClientBuilder.create()
|
WxPayV3HttpClientBuilder wxPayV3HttpClientBuilder = WxPayV3HttpClientBuilder.create()
|
||||||
|
.withSignUriStripPrefix(this.getApiHostUrlPath())
|
||||||
.withMerchant(mchId, certSerialNo, merchantPrivateKey)
|
.withMerchant(mchId, certSerialNo, merchantPrivateKey)
|
||||||
.withValidator(new WxPayValidator(certificatesVerifier));
|
.withValidator(new WxPayValidator(certificatesVerifier));
|
||||||
// 当 apiHostUrl 配置为自定义代理地址时,将代理主机加入受信任列表,
|
// 当 apiHostUrl 配置为自定义代理地址时,将代理主机加入受信任列表,
|
||||||
|
|||||||
@@ -366,9 +366,9 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
|
|||||||
if (StringUtils.isNotBlank(this.getConfig().getApiV3Key())) {
|
if (StringUtils.isNotBlank(this.getConfig().getApiV3Key())) {
|
||||||
throw new WxRuntimeException("微信支付V3 目前不支持沙箱模式!");
|
throw new WxRuntimeException("微信支付V3 目前不支持沙箱模式!");
|
||||||
}
|
}
|
||||||
return this.getConfig().getApiHostUrl() + "/xdc/apiv2sandbox";
|
return this.getConfig().getApiHostWithPathPrefix() + "/xdc/apiv2sandbox";
|
||||||
}
|
}
|
||||||
return this.getConfig().getApiHostUrl();
|
return this.getConfig().getApiHostWithPathPrefix();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ import org.apache.http.impl.execchain.ClientExecChain;
|
|||||||
public class WxPayV3HttpClientBuilder extends HttpClientBuilder {
|
public class WxPayV3HttpClientBuilder extends HttpClientBuilder {
|
||||||
private Credentials credentials;
|
private Credentials credentials;
|
||||||
private Validator validator;
|
private Validator validator;
|
||||||
|
/**
|
||||||
|
* 签名前从请求 URI Path 中移除的前缀(用于带路径前缀的代理场景)
|
||||||
|
*/
|
||||||
|
private String signUriStripPrefix;
|
||||||
/**
|
/**
|
||||||
* 额外受信任的主机列表,用于代理转发场景:对这些主机的请求也会携带微信支付 Authorization 头
|
* 额外受信任的主机列表,用于代理转发场景:对这些主机的请求也会携带微信支付 Authorization 头
|
||||||
*/
|
*/
|
||||||
@@ -40,12 +44,30 @@ public class WxPayV3HttpClientBuilder extends HttpClientBuilder {
|
|||||||
|
|
||||||
public WxPayV3HttpClientBuilder withMerchant(String merchantId, String serialNo, PrivateKey privateKey) {
|
public WxPayV3HttpClientBuilder withMerchant(String merchantId, String serialNo, PrivateKey privateKey) {
|
||||||
this.credentials =
|
this.credentials =
|
||||||
new WxPayCredentials(merchantId, new PrivateKeySigner(serialNo, privateKey));
|
new WxPayCredentials(merchantId, new PrivateKeySigner(serialNo, privateKey), this.signUriStripPrefix);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public WxPayV3HttpClientBuilder withCredentials(Credentials credentials) {
|
public WxPayV3HttpClientBuilder withCredentials(Credentials credentials) {
|
||||||
this.credentials = credentials;
|
this.credentials = credentials;
|
||||||
|
if (this.credentials instanceof WxPayCredentials) {
|
||||||
|
((WxPayCredentials) this.credentials).setSignUriStripPrefix(this.signUriStripPrefix);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置签名前需要移除的 URI Path 前缀.
|
||||||
|
* 例如设置为 "/api-weixin" 时,签名串中的 Path 会从 "/api-weixin/v3/..." 调整为 "/v3/..."。
|
||||||
|
*
|
||||||
|
* @param signUriStripPrefix 需要移除的前缀
|
||||||
|
* @return 当前 Builder 实例
|
||||||
|
*/
|
||||||
|
public WxPayV3HttpClientBuilder withSignUriStripPrefix(String signUriStripPrefix) {
|
||||||
|
this.signUriStripPrefix = signUriStripPrefix;
|
||||||
|
if (this.credentials instanceof WxPayCredentials) {
|
||||||
|
((WxPayCredentials) this.credentials).setSignUriStripPrefix(signUriStripPrefix);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,16 +20,42 @@ public class WxPayCredentials implements Credentials {
|
|||||||
private static final SecureRandom RANDOM = new SecureRandom();
|
private static final SecureRandom RANDOM = new SecureRandom();
|
||||||
protected String merchantId;
|
protected String merchantId;
|
||||||
protected Signer signer;
|
protected Signer signer;
|
||||||
|
/**
|
||||||
|
* 签名前从 URI Path 中移除的前缀(用于带路径前缀的反向代理场景)
|
||||||
|
* 例如配置为 "/api-weixin" 时,"/api-weixin/v3/pay/..." 将参与签名为 "/v3/pay/..."
|
||||||
|
*/
|
||||||
|
protected String signUriStripPrefix;
|
||||||
|
|
||||||
public WxPayCredentials(String merchantId, Signer signer) {
|
public WxPayCredentials(String merchantId, Signer signer) {
|
||||||
this.merchantId = merchantId;
|
this.merchantId = merchantId;
|
||||||
this.signer = signer;
|
this.signer = signer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public WxPayCredentials(String merchantId, Signer signer, String signUriStripPrefix) {
|
||||||
|
this.merchantId = merchantId;
|
||||||
|
this.signer = signer;
|
||||||
|
this.setSignUriStripPrefix(signUriStripPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
public String getMerchantId() {
|
public String getMerchantId() {
|
||||||
return merchantId;
|
return merchantId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSignUriStripPrefix(String signUriStripPrefix) {
|
||||||
|
if (signUriStripPrefix == null || signUriStripPrefix.trim().isEmpty()) {
|
||||||
|
this.signUriStripPrefix = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String normalized = signUriStripPrefix.trim();
|
||||||
|
if (!normalized.startsWith("/")) {
|
||||||
|
normalized = "/" + normalized;
|
||||||
|
}
|
||||||
|
if (normalized.length() > 1 && normalized.endsWith("/")) {
|
||||||
|
normalized = normalized.substring(0, normalized.length() - 1);
|
||||||
|
}
|
||||||
|
this.signUriStripPrefix = normalized;
|
||||||
|
}
|
||||||
|
|
||||||
protected long generateTimestamp() {
|
protected long generateTimestamp() {
|
||||||
return System.currentTimeMillis() / 1000;
|
return System.currentTimeMillis() / 1000;
|
||||||
}
|
}
|
||||||
@@ -70,7 +96,7 @@ public class WxPayCredentials implements Credentials {
|
|||||||
protected final String buildMessage(String nonce, long timestamp, HttpRequestWrapper request)
|
protected final String buildMessage(String nonce, long timestamp, HttpRequestWrapper request)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
URI uri = request.getURI();
|
URI uri = request.getURI();
|
||||||
String canonicalUrl = uri.getRawPath();
|
String canonicalUrl = stripPathPrefix(uri.getRawPath());
|
||||||
if (uri.getQuery() != null) {
|
if (uri.getQuery() != null) {
|
||||||
canonicalUrl += "?" + uri.getRawQuery();
|
canonicalUrl += "?" + uri.getRawQuery();
|
||||||
}
|
}
|
||||||
@@ -90,4 +116,18 @@ public class WxPayCredentials implements Credentials {
|
|||||||
+ body + "\n";
|
+ body + "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String stripPathPrefix(String rawPath) {
|
||||||
|
if (rawPath == null || rawPath.isEmpty() || signUriStripPrefix == null) {
|
||||||
|
return rawPath;
|
||||||
|
}
|
||||||
|
if (!rawPath.startsWith(signUriStripPrefix)) {
|
||||||
|
return rawPath;
|
||||||
|
}
|
||||||
|
String stripped = rawPath.substring(signUriStripPrefix.length());
|
||||||
|
if (stripped.isEmpty()) {
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
return stripped.startsWith("/") ? stripped : "/" + stripped;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.github.binarywang.wxpay.config;
|
|||||||
|
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import static org.testng.Assert.assertEquals;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <pre>
|
* <pre>
|
||||||
* Created by BinaryWang on 2017/6/18.
|
* Created by BinaryWang on 2017/6/18.
|
||||||
@@ -38,6 +40,15 @@ public class WxPayConfigTest {
|
|||||||
payConfig.hashCode();
|
payConfig.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testApiHostUrlPath() {
|
||||||
|
payConfig.setApiHostUrl("http://10.0.0.1:3128/");
|
||||||
|
payConfig.setApiHostUrlPath("api-weixin/");
|
||||||
|
assertEquals(payConfig.getApiHostUrl(), "http://10.0.0.1:3128");
|
||||||
|
assertEquals(payConfig.getApiHostUrlPath(), "/api-weixin");
|
||||||
|
assertEquals(payConfig.getApiHostWithPathPrefix(), "http://10.0.0.1:3128/api-weixin");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInitSSLContext_base64() throws Exception {
|
public void testInitSSLContext_base64() throws Exception {
|
||||||
payConfig.setMchId("123");
|
payConfig.setMchId("123");
|
||||||
|
|||||||
Reference in New Issue
Block a user