diff --git a/solon-plugins/wx-java-pay-solon-plugin/README.md b/solon-plugins/wx-java-pay-solon-plugin/README.md index b0e212593..8ff341629 100644 --- a/solon-plugins/wx-java-pay-solon-plugin/README.md +++ b/solon-plugins/wx-java-pay-solon-plugin/README.md @@ -23,6 +23,8 @@ wx: pay: appId: xxxxxxxxxxx mchId: 15xxxxxxxxx #商户id + apiHostUrl: http://10.0.0.1:3128 # 可选:代理主机 + apiHostUrlPath: /api-weixin # 可选:代理入口前缀 apiV3Key: Dc1DBwSc094jACxxxxxxxxxxxxxxx #V3密钥 certSerialNo: 62C6CEAA360BCxxxxxxxxxxxxxxx privateKeyPath: classpath:cert/apiclient_key.pem #apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径 diff --git a/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/config/WxPayAutoConfiguration.java b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/config/WxPayAutoConfiguration.java index 3ef7456da..665ae8fc2 100644 --- a/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/config/WxPayAutoConfiguration.java +++ b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/config/WxPayAutoConfiguration.java @@ -59,6 +59,7 @@ public WxPayService wxPayService() { payConfig.setPublicKeyId(StringUtils.trimToNull(this.properties.getPublicKeyId())); payConfig.setPublicKeyPath(StringUtils.trimToNull(this.properties.getPublicKeyPath())); payConfig.setApiHostUrl(StringUtils.trimToNull(this.properties.getApiHostUrl())); + payConfig.setApiHostUrlPath(StringUtils.trimToNull(this.properties.getApiHostUrlPath())); payConfig.setStrictlyNeedWechatPaySerial(this.properties.isStrictlyNeedWechatPaySerial()); payConfig.setFullPublicKeyModel(this.properties.isFullPublicKeyModel()); diff --git a/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/properties/WxPayProperties.java b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/properties/WxPayProperties.java index d394fefbd..d8d287e03 100644 --- a/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/properties/WxPayProperties.java +++ b/solon-plugins/wx-java-pay-solon-plugin/src/main/java/com/binarywang/solon/wxjava/pay/properties/WxPayProperties.java @@ -113,6 +113,12 @@ public class WxPayProperties { */ private String apiHostUrl; + /** + * 自定义API主机路径前缀(用于代理入口前缀) + * 例如:/api-weixin + */ + private String apiHostUrlPath; + /** * 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认不添加 */ diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/README.md b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/README.md index d8d41b7de..b07a8f825 100644 --- a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/README.md +++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/README.md @@ -255,6 +255,7 @@ public class PayService { | payScorePermissionNotifyUrl | 支付分授权回调地址 | 无 | | useSandboxEnv | 是否使用沙箱环境 | false | | apiHostUrl | 自定义API主机地址 | https://api.mch.weixin.qq.com | +| apiHostUrlPath | 自定义API主机路径前缀(代理入口前缀) | 空 | | strictlyNeedWechatPaySerial | 是否所有V3请求都添加序列号头 | false | | fullPublicKeyModel | 是否完全使用公钥模式 | false | | publicKeyId | 公钥ID | 无 | diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPaySingleProperties.java b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPaySingleProperties.java index a5cda55fb..c0686b9d9 100644 --- a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPaySingleProperties.java +++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPaySingleProperties.java @@ -112,6 +112,12 @@ public class WxPaySingleProperties implements Serializable { */ private String apiHostUrl; + /** + * 自定义API主机路径前缀(用于代理入口前缀). + * 例如:/api-weixin + */ + private String apiHostUrlPath; + /** * 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认不添加. */ diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/service/WxPayMultiServicesImpl.java b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/service/WxPayMultiServicesImpl.java index 459fe3b6c..c3eb88942 100644 --- a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/service/WxPayMultiServicesImpl.java +++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/service/WxPayMultiServicesImpl.java @@ -83,6 +83,7 @@ private WxPayService buildWxPayService(WxPaySingleProperties properties) { payConfig.setPublicKeyId(StringUtils.trimToNull(properties.getPublicKeyId())); payConfig.setPublicKeyPath(StringUtils.trimToNull(properties.getPublicKeyPath())); payConfig.setApiHostUrl(StringUtils.trimToNull(properties.getApiHostUrl())); + payConfig.setApiHostUrlPath(StringUtils.trimToNull(properties.getApiHostUrlPath())); payConfig.setStrictlyNeedWechatPaySerial(properties.isStrictlyNeedWechatPaySerial()); payConfig.setFullPublicKeyModel(properties.isFullPublicKeyModel()); diff --git a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/test/java/com/binarywang/spring/starter/wxjava/pay/WxPayMultiServicesTest.java b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/test/java/com/binarywang/spring/starter/wxjava/pay/WxPayMultiServicesTest.java index 25a091da0..d60335ebe 100644 --- a/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/test/java/com/binarywang/spring/starter/wxjava/pay/WxPayMultiServicesTest.java +++ b/spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/test/java/com/binarywang/spring/starter/wxjava/pay/WxPayMultiServicesTest.java @@ -26,6 +26,8 @@ "wx.pay.configs.app1.notify-url=https://example.com/pay/notify", "wx.pay.configs.app2.app-id=wx2222222222222222", "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.cert-serial-no=2222222222222222", "wx.pay.configs.app2.private-key-path=classpath:cert/apiclient_key.pem", @@ -57,6 +59,8 @@ public void testConfiguration() { assertNotNull(app2Config, "app2 configuration should exist"); assertEquals("wx2222222222222222", app2Config.getAppId()); assertEquals("2222222222", app2Config.getMchId()); + assertEquals("http://10.0.0.1:3128", app2Config.getApiHostUrl()); + assertEquals("/api-weixin", app2Config.getApiHostUrlPath()); assertEquals("22222222222222222222222222222222", app2Config.getApiv3Key()); } @@ -71,6 +75,7 @@ public void testGetWxPayService() { assertNotNull(app2Service, "Should get WxPayService for app2"); assertEquals("wx2222222222222222", app2Service.getConfig().getAppId()); assertEquals("2222222222", app2Service.getConfig().getMchId()); + assertEquals("/api-weixin", app2Service.getConfig().getApiHostUrlPath()); // 测试相同key返回相同实例 WxPayService app1ServiceAgain = wxPayMultiServices.getWxPayService("app1"); diff --git a/spring-boot-starters/wx-java-pay-spring-boot-starter/README.md b/spring-boot-starters/wx-java-pay-spring-boot-starter/README.md index d87a38fb9..bed890d5e 100644 --- a/spring-boot-starters/wx-java-pay-spring-boot-starter/README.md +++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/README.md @@ -23,6 +23,8 @@ wx: pay: appId: xxxxxxxxxxx mchId: 15xxxxxxxxx #商户id + apiHostUrl: http://10.0.0.1:3128 # 可选:代理主机 + apiHostUrlPath: /api-weixin # 可选:代理入口前缀 apiV3Key: Dc1DBwSc094jACxxxxxxxxxxxxxxx #V3密钥 certSerialNo: 62C6CEAA360BCxxxxxxxxxxxxxxx privateKeyPath: classpath:cert/apiclient_key.pem #apiclient_key.pem证书文件的绝对路径或者以classpath:开头的类路径 diff --git a/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java index 758fd929a..6f0f137cd 100644 --- a/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java +++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java @@ -63,6 +63,7 @@ public WxPayService wxPayService() { payConfig.setPublicKeyId(StringUtils.trimToNull(this.properties.getPublicKeyId())); payConfig.setPublicKeyPath(StringUtils.trimToNull(this.properties.getPublicKeyPath())); payConfig.setApiHostUrl(StringUtils.trimToNull(this.properties.getApiHostUrl())); + payConfig.setApiHostUrlPath(StringUtils.trimToNull(this.properties.getApiHostUrlPath())); payConfig.setStrictlyNeedWechatPaySerial(this.properties.isStrictlyNeedWechatPaySerial()); payConfig.setFullPublicKeyModel(this.properties.isFullPublicKeyModel()); diff --git a/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java index 25f7d7c02..38a5e253a 100644 --- a/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java +++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java @@ -111,6 +111,12 @@ public class WxPayProperties { */ private String apiHostUrl; + /** + * 自定义API主机路径前缀(用于代理入口前缀) + * 例如:/api-weixin + */ + private String apiHostUrlPath; + /** * 是否将全部v3接口的请求都添加Wechatpay-Serial请求头,默认不添加 */ diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/VerifierBuilder.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/VerifierBuilder.java index b0d9276a3..4c8aafb8e 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/VerifierBuilder.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/VerifierBuilder.java @@ -6,6 +6,8 @@ import lombok.NoArgsConstructor; import org.apache.commons.lang3.StringUtils; +import java.net.URI; +import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.security.PrivateKey; import java.security.PublicKey; @@ -118,8 +120,19 @@ private static AutoUpdateCertificatesVerifier getCertificatesVerifier( String certSerialNo, String mchId, String apiV3Key, PrivateKey merchantPrivateKey, 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( - new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)), + new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey), signUriStripPrefix), apiV3Key.getBytes(StandardCharsets.UTF_8), certAutoUpdateTime, payBaseUrl, wxPayHttpProxy); } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java index 1e0e8d2c4..451a883fe 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java @@ -64,6 +64,12 @@ public class WxPayConfig { */ private String apiHostUrl = DEFAULT_PAY_BASE_URL; + /** + * 微信支付接口请求地址路径前缀(用于网关代理前缀). + * 例如:/api-weixin + */ + private String apiHostUrlPath; + /** * http请求连接超时时间. */ @@ -285,11 +291,42 @@ public class WxPayConfig { * @return 微信支付接口请求地址域名 */ public String getApiHostUrl() { - if (StringUtils.isEmpty(this.apiHostUrl)) { + String hostUrl = StringUtils.trimToNull(this.apiHostUrl); + if (hostUrl == null) { return DEFAULT_PAY_BASE_URL; } + if (hostUrl.endsWith("/")) { + hostUrl = hostUrl.substring(0, hostUrl.length() - 1); + } + return hostUrl; + } + + /** + * 返回所设置的微信支付接口路径前缀. + * + * @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; + } - return this.apiHostUrl; + /** + * 返回用于请求层拼接的基础地址:host + pathPrefix. + * + * @return 拼接后的基础地址 + */ + public String getApiHostWithPathPrefix() { + return this.getApiHostUrl() + this.getApiHostUrlPath(); } @SneakyThrows @@ -391,10 +428,11 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException { } else { certificatesVerifier = VerifierBuilder.build( 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() + .withSignUriStripPrefix(this.getApiHostUrlPath()) .withMerchant(mchId, certSerialNo, merchantPrivateKey) .withValidator(new WxPayValidator(certificatesVerifier)); // 当 apiHostUrl 配置为自定义代理地址时,将代理主机加入受信任列表, diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java index 36987f637..0762908fc 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java @@ -363,9 +363,9 @@ public String getPayBaseUrl() { if (StringUtils.isNotBlank(this.getConfig().getApiV3Key())) { 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 diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/WxPayV3HttpClientBuilder.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/WxPayV3HttpClientBuilder.java index 91baa1624..63a92b25c 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/WxPayV3HttpClientBuilder.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/WxPayV3HttpClientBuilder.java @@ -15,6 +15,10 @@ public class WxPayV3HttpClientBuilder extends HttpClientBuilder { private Credentials credentials; private Validator validator; + /** + * 签名前从请求 URI Path 中移除的前缀(用于带路径前缀的代理场景) + */ + private String signUriStripPrefix; /** * 额外受信任的主机列表,用于代理转发场景:对这些主机的请求也会携带微信支付 Authorization 头 */ @@ -40,12 +44,30 @@ public static WxPayV3HttpClientBuilder create() { public WxPayV3HttpClientBuilder withMerchant(String merchantId, String serialNo, PrivateKey privateKey) { this.credentials = - new WxPayCredentials(merchantId, new PrivateKeySigner(serialNo, privateKey)); + new WxPayCredentials(merchantId, new PrivateKeySigner(serialNo, privateKey), this.signUriStripPrefix); return this; } public WxPayV3HttpClientBuilder withCredentials(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; } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayCredentials.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayCredentials.java index 80eea8f68..4b78a26f7 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayCredentials.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayCredentials.java @@ -20,16 +20,42 @@ public class WxPayCredentials implements Credentials { private static final SecureRandom RANDOM = new SecureRandom(); protected String merchantId; protected Signer signer; + /** + * 签名前从 URI Path 中移除的前缀(用于带路径前缀的反向代理场景) + * 例如配置为 "/api-weixin" 时,"/api-weixin/v3/pay/..." 将参与签名为 "/v3/pay/..." + */ + protected String signUriStripPrefix; public WxPayCredentials(String merchantId, Signer signer) { this.merchantId = merchantId; this.signer = signer; } + public WxPayCredentials(String merchantId, Signer signer, String signUriStripPrefix) { + this.merchantId = merchantId; + this.signer = signer; + this.setSignUriStripPrefix(signUriStripPrefix); + } + public String getMerchantId() { 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() { return System.currentTimeMillis() / 1000; } @@ -70,7 +96,7 @@ public final String getToken(HttpRequestWrapper request) throws IOException { protected final String buildMessage(String nonce, long timestamp, HttpRequestWrapper request) throws IOException { URI uri = request.getURI(); - String canonicalUrl = uri.getRawPath(); + String canonicalUrl = stripPathPrefix(uri.getRawPath()); if (uri.getQuery() != null) { canonicalUrl += "?" + uri.getRawQuery(); } @@ -90,4 +116,18 @@ protected final String buildMessage(String nonce, long timestamp, HttpRequestWra + 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; + } + } diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/config/WxPayConfigTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/config/WxPayConfigTest.java index 46bc23aac..0b5d1b732 100644 --- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/config/WxPayConfigTest.java +++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/config/WxPayConfigTest.java @@ -2,6 +2,8 @@ import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; + /** *
  *  Created by BinaryWang on 2017/6/18.
@@ -38,6 +40,15 @@ public void testHashCode() {
     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
   public void testInitSSLContext_base64() throws Exception {
     payConfig.setMchId("123");