This commit is contained in:
GukSang.Jin
2026-03-18 15:57:03 +08:00
parent fbe8d83573
commit 65f4fa02ca
3 changed files with 76 additions and 38 deletions

View File

@@ -34,10 +34,12 @@ LOG_LEVEL=Information
# WECHATPAY__APPID=wx1234567890abcdef
# WECHATPAY__MERCHANTID=1900000001
# WECHATPAY__CERTIFICATESERIALNUMBER=7777777777777777777777777777777777777777
# WECHATPAY__PRIVATEKEYPATH=certs/apiclient_key.pem
# WECHATPAY__PRIVATEKEYPEM=certs/apiclient_key.pem
# WECHATPAY__PRIVATEKEYPATH=
# WECHATPAY__NOTIFYURL=https://your-domain.example.com/api/payments/wechat/notify
# WECHATPAY__APIV3KEY=32bytesapiv3keyxxxxxxxxxxxxxxxx
# WECHATPAY__PLATFORMPUBLICKEYPATH=certs/wechatpay_public_key.pem
# WECHATPAY__PLATFORMPUBLICKEYPEM=certs/wechatpay_public_key.pem
# WECHATPAY__PLATFORMPUBLICKEYPATH=
# WECHATPAY__PLATFORMPUBLICKEYSERIAL=PUB_KEY_ID_xxxxxxxxxxxxxxxxxxxxxxxx
# WECHATPAY__CURRENCY=CNY
# WECHATPAY__ORDEREXPIREMINUTES=5

View File

@@ -273,28 +273,10 @@ public sealed class WeChatPayService
private string GetPrivateKeyPem()
{
if (!string.IsNullOrWhiteSpace(_options.PrivateKeyPem))
{
return _options.PrivateKeyPem;
}
var keyPath = _options.PrivateKeyPath;
if (string.IsNullOrWhiteSpace(keyPath))
{
throw new InvalidOperationException("WeChat private key is not configured.");
}
if (!Path.IsPathRooted(keyPath))
{
keyPath = Path.Combine(AppContext.BaseDirectory, keyPath);
}
if (!File.Exists(keyPath))
{
throw new InvalidOperationException($"WeChat private key file was not found: {keyPath}");
}
return File.ReadAllText(keyPath);
return ResolvePemContent(
_options.PrivateKeyPem,
_options.PrivateKeyPath,
"WeChat private key");
}
private void ValidateOptions()
@@ -320,10 +302,14 @@ public sealed class WeChatPayService
{
missingFields.Add("WeChatPay:NotifyUrl");
}
else
{
ValidateNotifyUrl(_options.NotifyUrl);
}
if (string.IsNullOrWhiteSpace(_options.PrivateKeyPem) && string.IsNullOrWhiteSpace(_options.PrivateKeyPath))
{
missingFields.Add("WeChatPay:PrivateKeyPath or WeChatPay:PrivateKeyPem");
missingFields.Add("WeChatPay:PrivateKeyPem (PEM content or file path)");
}
if (missingFields.Count > 0)
@@ -341,11 +327,15 @@ public sealed class WeChatPayService
{
missingFields.Add("WeChatPay:ApiV3Key");
}
else
{
ValidateApiV3Key(_options.ApiV3Key);
}
if (string.IsNullOrWhiteSpace(_options.PlatformPublicKeyPem) &&
string.IsNullOrWhiteSpace(_options.PlatformPublicKeyPath))
{
missingFields.Add("WeChatPay:PlatformPublicKeyPath or WeChatPay:PlatformPublicKeyPem");
missingFields.Add("WeChatPay:PlatformPublicKeyPem (PEM content or file path)");
}
if (string.IsNullOrWhiteSpace(_options.PlatformPublicKeySerial))
@@ -392,28 +382,74 @@ public sealed class WeChatPayService
private string GetPlatformPublicKeyPem()
{
if (!string.IsNullOrWhiteSpace(_options.PlatformPublicKeyPem))
return ResolvePemContent(
_options.PlatformPublicKeyPem,
_options.PlatformPublicKeyPath,
"WeChat platform public key");
}
private static string ResolvePemContent(string pemOrPath, string fallbackPath, string keyName)
{
var configuredValue = !string.IsNullOrWhiteSpace(pemOrPath)
? pemOrPath.Trim()
: fallbackPath.Trim();
if (string.IsNullOrWhiteSpace(configuredValue))
{
return _options.PlatformPublicKeyPem;
throw new InvalidOperationException($"{keyName} is not configured.");
}
var keyPath = _options.PlatformPublicKeyPath;
if (string.IsNullOrWhiteSpace(keyPath))
if (LooksLikePemContent(configuredValue))
{
throw new InvalidOperationException("WeChat platform public key is not configured.");
return configuredValue;
}
if (!Path.IsPathRooted(keyPath))
var resolvedPath = configuredValue;
if (!Path.IsPathRooted(resolvedPath))
{
keyPath = Path.Combine(AppContext.BaseDirectory, keyPath);
resolvedPath = Path.Combine(AppContext.BaseDirectory, resolvedPath);
}
if (!File.Exists(keyPath))
if (!File.Exists(resolvedPath))
{
throw new InvalidOperationException($"WeChat platform public key file was not found: {keyPath}");
throw new InvalidOperationException($"{keyName} file was not found: {resolvedPath}");
}
return File.ReadAllText(keyPath);
return File.ReadAllText(resolvedPath);
}
private static bool LooksLikePemContent(string value)
{
return value.Contains("-----BEGIN", StringComparison.Ordinal);
}
private static void ValidateNotifyUrl(string notifyUrl)
{
if (!Uri.TryCreate(notifyUrl, UriKind.Absolute, out var uri))
{
throw new InvalidOperationException("WeChatPay:NotifyUrl must be a valid absolute URL.");
}
var isLocalhost = uri.Host.Equals("localhost", StringComparison.OrdinalIgnoreCase) ||
uri.Host.Equals("127.0.0.1", StringComparison.OrdinalIgnoreCase);
if (!isLocalhost && !uri.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("WeChatPay:NotifyUrl should use https for public callback addresses.");
}
}
private static void ValidateApiV3Key(string apiV3Key)
{
if (LooksLikePemContent(apiV3Key) || apiV3Key.StartsWith("MII", StringComparison.Ordinal))
{
throw new InvalidOperationException("WeChatPay:ApiV3Key looks like a certificate or private key. It must be the 32-byte APIv3 key from WeChat Pay.");
}
if (Encoding.UTF8.GetByteCount(apiV3Key) != 32)
{
throw new InvalidOperationException("WeChatPay:ApiV3Key must be exactly 32 bytes.");
}
}
private string DecryptNotificationResource(PaymentNotificationResource resource)

View File

@@ -15,8 +15,8 @@
"AppId": "wxa27a3e3cfce7ae19",
"MerchantId": "1107066208",
"CertificateSerialNumber": "3243AE8427384A692FBAA92C5EC5887BEF1988FD",
"PrivateKeyPath": "/etc/ssl/certs/apiclient_key.pem",
"PrivateKeyPem": "",
"PrivateKeyPath": "",
"PrivateKeyPem": "/etc/ssl/certs/apiclient_key.pem",
"NotifyUrl": "http://csicsizn.com:8080/api/payments/wechat/notify",
"ApiV3Key": "MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDMlj1LkO9Cfg3LWBYpe9GBn7vWgLE6kqEG1ohaxbaPxA6OwuGn0XQZfRBbJmncSXGLYahQ7T0OvFBIp8SyYm6q9kol8c9naxd+KxjMrx/qSWqwEJ76meBNK6LBYBVFTobg47cexpyR1TOZK0EFBGJQU2yQ1nsuQczVvq+WaSn4+kVENWf+o2g2nFS1VXNBIjL0/C8vXbz/0Y8k6ecH5mbmy/t+YR6X4TsiIAzIxIcfMMNhVCwqKLsu3D20N0ViYbKToHWIXi8wS8dyruHqQ1lZVJV/fF7pdI36HFI94enksCZrDb1LVFjL+4ccE04MJLIEZSH73RrOFkLaRzn8pwBbAgMBAAECggEAY7kD7baa+XVKMgkg3F2vVJjQzZDzUpKwjQ27b0uaXl95nRrfNZcCGX59n4CM70SZZRBYJAJP1cP",
"PlatformPublicKeyPath": "",