Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,13 @@ private String getWechatPaySerial(WxPayConfig wxPayConfig) {
return wxPayConfig.getPublicKeyId();
}

return wxPayConfig.getVerifier().getValidCertificate().getSerialNumber().toString(16).toUpperCase();
try {
return wxPayConfig.getVerifier().getValidCertificate().getSerialNumber().toString(16).toUpperCase();
} catch (Exception e) {
log.warn("Failed to get certificate serial number: {}", e.getMessage());
// 返回空字符串而不是抛出异常,让请求继续进行,由微信服务器判断是否需要Wechatpay-Serial
return "";
}
}

private void logRequestAndResponse(String url, String requestStr, String responseStr) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,13 @@ private String getWechatPaySerial(WxPayConfig wxPayConfig) {
return wxPayConfig.getPublicKeyId();
}

return wxPayConfig.getVerifier().getValidCertificate().getSerialNumber().toString(16).toUpperCase();
try {
return wxPayConfig.getVerifier().getValidCertificate().getSerialNumber().toString(16).toUpperCase();
} catch (Exception e) {
log.warn("Failed to get certificate serial number: {}", e.getMessage());
// 返回空字符串而不是抛出异常,让请求继续进行,由微信服务器判断是否需要Wechatpay-Serial
return "";
}
}

private void logRequestAndResponse(String url, String requestStr, String responseStr) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,18 +109,24 @@ public AutoUpdateCertificatesVerifier(Credentials credentials, byte[] apiV3Key,
this.minutesInterval = minutesInterval;
this.payBaseUrl = payBaseUrl;
this.wxPayHttpProxy = wxPayHttpProxy;
//构造时更新证书
//构造时尝试更新证书,但失败时不抛出异常,避免影响公钥模式的使用
try {
autoUpdateCert();
instant = Instant.now();
} catch (IOException | GeneralSecurityException e) {
throw new WxRuntimeException(e);
log.warn("Auto update cert failed during initialization, will retry later, exception = {}", e.getMessage());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里的容错只捕获了 IOException | GeneralSecurityException,但 autoUpdateCert() 在 HTTP 非 200(以及签名等运行时失败)时会直接抛 WxRuntimeException,仍可能导致初始化阶段异常退出(与 PR 目标的 404 场景不一致)。建议确认是否需要把该运行时异常也纳入容错范围。

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎

// 设置 instant 为 null,后续每次使用时都会尝试下载证书直到成功
instant = null;
}
}

@Override
public boolean verify(String serialNumber, byte[] message, String signature) {
checkAndAutoUpdateCert();
if (verifier == null) {
log.warn("No valid certificate available for verification");
return false;
}
return verifier.verify(serialNumber, message, signature);
}

Expand Down Expand Up @@ -220,6 +226,9 @@ private List<X509Certificate> deserializeToCerts(byte[] apiV3Key, String body) t
@Override
public X509Certificate getValidCertificate() {
checkAndAutoUpdateCert();
if (verifier == null) {
throw new WxRuntimeException("No valid certificate available, please check your configuration or use fullPublicKeyModel mode");
}
return verifier.getValidCertificate();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.github.binarywang.wxpay.v3.auth;

import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;

import static org.testng.Assert.*;

/**
* 测试公钥模式下 AutoUpdateCertificatesVerifier 的健壮性
*
* @author copilot
*/
public class AutoUpdateCertificatesVerifierPublicKeyModeTest {

private String invalidMchId;
private String invalidApiV3Key;
private String invalidCertSerialNo;
private String payBaseUrl;
private WxPayCredentials credentials;

@BeforeMethod
public void setUp() {
// 使用无效的配置,模拟证书下载失败的场景
invalidMchId = "invalid_mch_id";
invalidApiV3Key = "invalid_api_v3_key_must_be_32_b";
invalidCertSerialNo = "invalid_serial_no";
payBaseUrl = "https://api.mch.weixin.qq.com";

credentials = new WxPayCredentials(
invalidMchId,
new PrivateKeySigner(invalidCertSerialNo, null)
);
}

/**
* 测试当证书下载失败时,构造函数不应该抛出异常
* 这是为了支持公钥模式下的场景,在公钥模式下商户可能没有平台证书
*/
@Test
public void testConstructorShouldNotThrowExceptionWhenCertDownloadFails() {
// 构造函数应该不抛出异常,即使证书下载失败
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
credentials,
invalidApiV3Key.getBytes(StandardCharsets.UTF_8),
60,
payBaseUrl,
null
);
// 如果没有抛出异常,测试通过
assertNotNull(verifier);
}

/**
* 测试当没有有效证书时,verify 方法应该返回 false 而不是抛出异常
*/
@Test
public void testVerifyShouldReturnFalseWhenNoCertificateAvailable() {
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
credentials,
invalidApiV3Key.getBytes(StandardCharsets.UTF_8),
60,
payBaseUrl,
null
);

// verify 方法应该返回 false,而不是抛出异常
boolean result = verifier.verify("test_serial", "test_message".getBytes(), "test_signature");
assertFalse(result, "当没有有效证书时,verify 应该返回 false");
}

/**
* 测试当没有有效证书时,getValidCertificate 方法应该抛出有意义的异常
*/
@Test(expectedExceptions = me.chanjar.weixin.common.error.WxRuntimeException.class,
expectedExceptionsMessageRegExp = ".*No valid certificate available.*")
public void testGetValidCertificateShouldThrowMeaningfulException() {
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
credentials,
invalidApiV3Key.getBytes(StandardCharsets.UTF_8),
60,
payBaseUrl,
null
);
Comment on lines +60 to +86
Copy link

Copilot AI Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

三个测试方法中都重复了相同的测试数据初始化代码(invalidMchId、invalidApiV3Key、invalidCertSerialNo、payBaseUrl、credentials)。建议考虑将这些通用的测试数据提取到 @BeforeMethod 或私有辅助方法中,以减少代码重复并提高可维护性。

Copilot uses AI. Check for mistakes.

// 应该抛出有意义的异常
X509Certificate certificate = verifier.getValidCertificate();
}
}