feat(tenpayv3): 兼容平台证书与公钥灰度切换

This commit is contained in:
Fu Diwei
2025-09-23 22:50:12 +08:00
parent 6f2ce08ae2
commit ea4aa7f5c7
11 changed files with 495 additions and 390 deletions

View File

@@ -69,7 +69,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.ExtendedSDK.Global
PlatformAuthScheme = _ROOT_.Settings.PlatformAuthScheme.Certificate, PlatformAuthScheme = _ROOT_.Settings.PlatformAuthScheme.Certificate,
PlatformCertificateManager = options.PlatformCertificateManager, PlatformCertificateManager = options.PlatformCertificateManager,
AutoEncryptRequestSensitiveProperty = options.AutoEncryptRequestSensitiveProperty, AutoEncryptRequestSensitiveProperty = options.AutoEncryptRequestSensitiveProperty,
AutoDecryptResponseSensitiveProperty = options.AutoDecryptResponseSensitiveProperty AutoDecryptResponseSensitiveProperty = options.AutoDecryptResponseSensitiveProperty,
}, httpClient, disposeClient); }, httpClient, disposeClient);
Credentials = new Settings.Credentials(options); Credentials = new Settings.Credentials(options);
@@ -100,7 +100,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.ExtendedSDK.Global
/// <returns></returns> /// <returns></returns>
public IFlurlRequest CreateFlurlRequest(WechatTenpayGlobalRequest request, HttpMethod httpMethod, params object[] urlSegments) public IFlurlRequest CreateFlurlRequest(WechatTenpayGlobalRequest request, HttpMethod httpMethod, params object[] urlSegments)
{ {
return base.CreateFlurlRequest(request, httpMethod, urlSegments); return ProxyClient.CreateFlurlRequest(request, httpMethod, urlSegments);
} }
/// <summary> /// <summary>

View File

@@ -61,7 +61,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{ {
if (client is null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
return WechatTenpayClientSigningExtensions.VerifySignature( return WechatTenpayClientSigningExtensions._VerifySignature(
client, client,
strTimestamp: webhookTimestamp, strTimestamp: webhookTimestamp,
strNonce: webhookNonce, strNonce: webhookNonce,
@@ -127,7 +127,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{ {
if (client is null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
return WechatTenpayClientSigningExtensions.VerifySignatureAsync( return WechatTenpayClientSigningExtensions._VerifySignatureAsync(
client, client,
strTimestamp: webhookTimestamp, strTimestamp: webhookTimestamp,
strNonce: webhookNonce, strNonce: webhookNonce,

View File

@@ -25,16 +25,37 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
if (client is null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
switch (client.PlatformAuthScheme) bool required = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute));
if (!required)
return request;
try
{ {
case PlatformAuthScheme.Certificate: switch (client.PlatformAuthScheme)
return EncryptRequestSensitivePropertyByCertificate<TRequest>(client, request); {
case PlatformAuthScheme.Certificate:
{
WechatTenpayClientRequestSerialNumberExtensions._EnsureRequestWechatpaySerialNumberIsSet(client, request);
return EncryptRequestSensitivePropertyUseCertificateManager<TRequest>(client.PlatformCertificateManager, client.Credentials.SignScheme, request);
}
case PlatformAuthScheme.PublicKey: case PlatformAuthScheme.PublicKey:
return EncryptRequestSensitivePropertyByPublicKey<TRequest>(client, request); {
WechatTenpayClientRequestSerialNumberExtensions._EnsureRequestWechatpaySerialNumberIsSet(client, request);
return EncryptRequestSensitivePropertyUsePublicKeyManager<TRequest>(client.PlatformPublicKeyManager, client.Credentials.SignScheme, request);
}
default: default:
throw new WechatTenpayException($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\"."); throw new NotSupportedException($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\".");
}
}
catch (WechatTenpayException)
{
throw;
}
catch (Exception ex)
{
throw new WechatTenpayException("Failed to encrypt request. Please see the inner exception for more details.", ex);
} }
} }
@@ -45,84 +66,47 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
public static Task<TRequest> EncryptRequestSensitivePropertyAsync<TRequest>(this WechatTenpayClient client, TRequest request, CancellationToken cancellationToken = default) public static async Task<TRequest> EncryptRequestSensitivePropertyAsync<TRequest>(this WechatTenpayClient client, TRequest request, CancellationToken cancellationToken = default)
where TRequest : WechatTenpayRequest where TRequest : WechatTenpayRequest
{ {
if (client is null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
switch (client.PlatformAuthScheme) bool required = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute));
{ if (!required)
case PlatformAuthScheme.Certificate: return request;
return EncryptRequestSensitivePropertyByCertificateAsync<TRequest>(client, request);
case PlatformAuthScheme.PublicKey:
return EncryptRequestSensitivePropertyByPublicKeyAsync<TRequest>(client, request);
default:
throw new WechatTenpayException($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\".");
}
}
private static TRequest EncryptRequestSensitivePropertyByCertificate<TRequest>(this WechatTenpayClient client, TRequest request)
where TRequest : WechatTenpayRequest
{
try try
{ {
bool requireEncrypt = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute)); switch (client.PlatformAuthScheme)
if (!requireEncrypt)
return request;
string signScheme = client.Credentials.SignScheme;
string algorithmType = // 签名方式与加密算法保持一致RSA_SHA256 签名需 RSA 加密SM3 签名需 SM2 加密
SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_RSA :
SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_SM2 :
throw new WechatTenpayException($"Failed to encrypt request. Unsupported signing scheme: \"{signScheme}\".");
string certificate;
if (string.IsNullOrEmpty(request.WechatpaySerialNumber))
{ {
// 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的 case PlatformAuthScheme.Certificate:
IEnumerable<CertificateEntry> entries = client.PlatformCertificateManager.AllEntries() {
.Where(e => e.AlgorithmType == algorithmType) if (client.PlatformCertificateManager is not ICertificateManagerAsync)
.OrderByDescending(e => e.ExpireTime); {
if (!entries.Any()) // 降级为同步调用
{ return EncryptRequestSensitivePropertyUseCertificateManager(client.PlatformCertificateManager, client.Credentials.SignScheme, request);
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is empty. Please make sure you have downloaded platform (NOT merchant) certificates first."); }
}
CertificateEntry entry = entries.First(); await WechatTenpayClientRequestSerialNumberExtensions._EnsureRequestWechatpaySerialNumberIsSetAsync(client, request, cancellationToken).ConfigureAwait(false);
certificate = entry.Certificate; return await EncryptRequestSensitivePropertyUseCertificateManagerAsync<TRequest>((ICertificateManagerAsync)client.PlatformCertificateManager, client.Credentials.SignScheme, request, cancellationToken).ConfigureAwait(false);
request.WechatpaySerialNumber = entry.SerialNumber; }
case PlatformAuthScheme.PublicKey:
{
if (client.PlatformPublicKeyManager is not IPublicKeyManagerAsync)
{
// 降级为同步调用
return EncryptRequestSensitivePropertyUsePublicKeyManager(client.PlatformPublicKeyManager, client.Credentials.SignScheme, request);
}
await WechatTenpayClientRequestSerialNumberExtensions._EnsureRequestWechatpaySerialNumberIsSetAsync(client, request, cancellationToken).ConfigureAwait(false);
return await EncryptRequestSensitivePropertyUsePublicKeyManagerAsync<TRequest>((IPublicKeyManagerAsync)client.PlatformPublicKeyManager, client.Credentials.SignScheme, request, cancellationToken).ConfigureAwait(false);
}
default:
throw new NotSupportedException($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\".");
} }
else
{
// 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值
CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(request.WechatpaySerialNumber!);
if (!entry.HasValue)
{
throw new WechatTenpayException($"Failed to encrypt request, because the platform certificate manager does not contain a certificate matched the serial number \"{request.WechatpaySerialNumber}\". Please make sure you have downloaded platform (NOT merchant) certificates first.");
}
certificate = entry.Value.Certificate;
}
ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) =>
{
if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute)))
return (false, oldValue);
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
.FirstOrDefault(attr => attr.Scheme == signScheme);
if (attribute is null)
return (false, oldValue);
string newValue = GenerateEncryptedValueByCertificate(attribute.Algorithm, certificate, oldValue);
return (true, newValue);
});
return request;
} }
catch (WechatTenpayException) catch (WechatTenpayException)
{ {
@@ -134,281 +118,128 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
} }
} }
private static async Task<TRequest> EncryptRequestSensitivePropertyByCertificateAsync<TRequest>(this WechatTenpayClient client, TRequest request, CancellationToken cancellationToken = default) private static TRequest EncryptRequestSensitivePropertyUseCertificateManager<TRequest>(ICertificateManager manager, string signScheme, TRequest request)
where TRequest : WechatTenpayRequest where TRequest : WechatTenpayRequest
{ {
if (client.PlatformCertificateManager is not ICertificateManagerAsync) if (manager is null)
{ throw new NullReferenceException("The platform certificate manager is not configured.");
// 降级为同步调用
return EncryptRequestSensitivePropertyByCertificate(client, request);
}
try CertificateEntry? entry = manager.GetEntry(request.WechatpaySerialNumber!);
{ if (!entry.HasValue)
bool requireEncrypt = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute)); throw new WechatTenpayException($"Failed to encrypt request, because the platform certificate manager does not contain a certificate matched the serial number \"{request.WechatpaySerialNumber}\". Please make sure you have downloaded platform (NOT merchant) certificates first.");
if (!requireEncrypt)
return request;
string signScheme = client.Credentials.SignScheme; return PopulateRequestEncryptedFieldsByCertificate(signScheme, entry.Value.Certificate, ref request);
string algorithmType = // 签名方式与加密算法保持一致RSA_SHA256 签名需 RSA 加密SM3 签名需 SM2 加密
SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_RSA :
SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_SM2 :
throw new WechatTenpayException($"Failed to encrypt request. Unsupported signing scheme: \"{signScheme}\".");
string certificate;
if (string.IsNullOrEmpty(request.WechatpaySerialNumber))
{
if (client.PlatformCertificateManager is null)
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is not initialized.");
// 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的
IEnumerable<CertificateEntry> entries = await ((ICertificateManagerAsync)client.PlatformCertificateManager).AllEntriesAsync(cancellationToken).ConfigureAwait(false);
entries = entries.Where(e => e.AlgorithmType == algorithmType).OrderByDescending(e => e.ExpireTime);
if (!entries.Any())
{
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is empty. Please make sure you have downloaded platform (NOT merchant) certificates first.");
}
CertificateEntry entry = entries.First();
certificate = entry.Certificate;
request.WechatpaySerialNumber = entry.SerialNumber;
}
else
{
// 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值
CertificateEntry? entry = await ((ICertificateManagerAsync)client.PlatformCertificateManager).GetEntryAsync(request.WechatpaySerialNumber!, cancellationToken).ConfigureAwait(false);
if (!entry.HasValue)
{
throw new WechatTenpayException($"Failed to encrypt request, because the platform certificate manager does not contain a certificate matched the serial number \"{request.WechatpaySerialNumber}\". Please make sure you have downloaded platform (NOT merchant) certificates first.");
}
certificate = entry.Value.Certificate;
}
ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) =>
{
if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute)))
return (false, oldValue);
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
.FirstOrDefault(attr => attr.Scheme == signScheme);
if (attribute is null)
return (false, oldValue);
string newValue = GenerateEncryptedValueByCertificate(attribute.Algorithm, certificate, oldValue);
return (true, newValue);
});
return request;
}
catch (WechatTenpayException)
{
throw;
}
catch (Exception ex)
{
throw new WechatTenpayException("Failed to encrypt request. Please see the inner exception for more details.", ex);
}
} }
private static TRequest EncryptRequestSensitivePropertyByPublicKey<TRequest>(this WechatTenpayClient client, TRequest request) private static async Task<TRequest> EncryptRequestSensitivePropertyUseCertificateManagerAsync<TRequest>(ICertificateManagerAsync manager, string signScheme, TRequest request, CancellationToken cancellationToken = default)
where TRequest : WechatTenpayRequest where TRequest : WechatTenpayRequest
{ {
try if (manager is null)
{ throw new NullReferenceException("The platform certificate manager is not configured.");
bool requireEncrypt = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute));
if (!requireEncrypt)
return request;
string signScheme = client.Credentials.SignScheme; CertificateEntry? entry = await manager.GetEntryAsync(request.WechatpaySerialNumber!, cancellationToken).ConfigureAwait(false);
string algorithmType = // 签名方式与加密算法保持一致RSA_SHA256 签名需 RSA 加密SM3 签名需 SM2 加密 if (!entry.HasValue)
SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(signScheme) ? PublicKeyEntry.ALGORITHM_TYPE_RSA : throw new WechatTenpayException($"Failed to encrypt request, because the platform certificate manager does not contain a certificate matched the serial number \"{request.WechatpaySerialNumber}\". Please make sure you have downloaded platform (NOT merchant) certificates first.");
SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? PublicKeyEntry.ALGORITHM_TYPE_SM2 :
throw new WechatTenpayException($"Failed to encrypt request. Unsupported signing scheme: \"{signScheme}\".");
string publicKey; return PopulateRequestEncryptedFieldsByCertificate(signScheme, entry.Value.Certificate, ref request);
if (string.IsNullOrEmpty(request.WechatpaySerialNumber))
{
// 如果未在请求中指定特定的平台公钥 ID从管理器中取第一个
IEnumerable<PublicKeyEntry> entries = client.PlatformPublicKeyManager.AllEntries()
.Where(e => e.AlgorithmType == algorithmType);
if (!entries.Any())
{
throw new WechatTenpayException("Failed to encrypt request, because the platform public key manager is empty.");
}
PublicKeyEntry entry = entries.First();
publicKey = entry.PublicKey;
request.WechatpaySerialNumber = entry.SerialNumber;
}
else
{
// 如果已在请求中指定特定的平台公钥 ID直接从管理器中取值
PublicKeyEntry? entry = client.PlatformPublicKeyManager.GetEntry(request.WechatpaySerialNumber!);
if (!entry.HasValue)
{
throw new WechatTenpayException($"Failed to encrypt request, because the platform public key manager does not contain a key matched the serial number \"{request.WechatpaySerialNumber}\".");
}
publicKey = entry.Value.PublicKey;
}
ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) =>
{
if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute)))
return (false, oldValue);
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
.FirstOrDefault(attr => attr.Scheme == signScheme);
if (attribute is null)
return (false, oldValue);
string newValue = GenerateEncryptedValueByPublicKey(attribute.Algorithm, publicKey, oldValue);
return (true, newValue);
});
return request;
}
catch (WechatTenpayException)
{
throw;
}
catch (Exception ex)
{
throw new WechatTenpayException("Failed to encrypt request. Please see the inner exception for more details.", ex);
}
} }
private static async Task<TRequest> EncryptRequestSensitivePropertyByPublicKeyAsync<TRequest>(this WechatTenpayClient client, TRequest request, CancellationToken cancellationToken = default) private static TRequest EncryptRequestSensitivePropertyUsePublicKeyManager<TRequest>(IPublicKeyManager manager, string signScheme, TRequest request)
where TRequest : WechatTenpayRequest where TRequest : WechatTenpayRequest
{ {
if (client.PlatformPublicKeyManager is not IPublicKeyManagerAsync) if (manager is null)
{ throw new NullReferenceException("The platform public key manager is not configured.");
// 降级为同步调用
return EncryptRequestSensitivePropertyByPublicKey(client, request);
}
try PublicKeyEntry? entry = manager.GetEntry(request.WechatpaySerialNumber!);
{ if (!entry.HasValue)
bool requireEncrypt = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute)); throw new WechatTenpayException($"Failed to encrypt request, because the platform public key manager does not contain a key matched the serial number \"{request.WechatpaySerialNumber}\".");
if (!requireEncrypt)
return request;
string signScheme = client.Credentials.SignScheme; return PopulateRequestEncryptedFieldsByPublicKey(signScheme, entry.Value.PublicKey, ref request);
string algorithmType = // 签名方式与加密算法保持一致RSA_SHA256 签名需 RSA 加密SM3 签名需 SM2 加密
SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(signScheme) ? PublicKeyEntry.ALGORITHM_TYPE_RSA :
SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? PublicKeyEntry.ALGORITHM_TYPE_SM2 :
throw new WechatTenpayException($"Failed to encrypt request. Unsupported signing scheme: \"{signScheme}\".");
string publicKey;
if (string.IsNullOrEmpty(request.WechatpaySerialNumber))
{
if (client.PlatformPublicKeyManager is null)
throw new WechatTenpayException("Failed to encrypt request, because the platform public key manager is not initialized.");
// 如果未在请求中指定特定的平台公钥 ID从管理器中第一个
IEnumerable<PublicKeyEntry> entries = await ((IPublicKeyManagerAsync)client.PlatformPublicKeyManager).AllEntriesAsync(cancellationToken).ConfigureAwait(false);
entries = entries.Where(e => e.AlgorithmType == algorithmType);
if (!entries.Any())
{
throw new WechatTenpayException("Failed to encrypt request, because the platform public key manager is empty.");
}
PublicKeyEntry entry = entries.First();
publicKey = entry.PublicKey;
request.WechatpaySerialNumber = entry.SerialNumber;
}
else
{
// 如果已在请求中指定特定的平台公钥 ID直接从管理器中取值
PublicKeyEntry? entry = await ((IPublicKeyManagerAsync)client.PlatformPublicKeyManager).GetEntryAsync(request.WechatpaySerialNumber!, cancellationToken).ConfigureAwait(false);
if (!entry.HasValue)
{
throw new WechatTenpayException($"Failed to encrypt request, because the platform public key manager does not contain a key matched the serial number \"{request.WechatpaySerialNumber}\".");
}
publicKey = entry.Value.PublicKey;
}
ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) =>
{
if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute)))
return (false, oldValue);
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
.FirstOrDefault(attr => attr.Scheme == signScheme);
if (attribute is null)
return (false, oldValue);
string newValue = GenerateEncryptedValueByPublicKey(attribute.Algorithm, publicKey, oldValue);
return (true, newValue);
});
return request;
}
catch (WechatTenpayException)
{
throw;
}
catch (Exception ex)
{
throw new WechatTenpayException("Failed to encrypt request. Please see the inner exception for more details.", ex);
}
} }
private static string GenerateEncryptedValueByCertificate(string algorithm, string certificate, string value) private static async Task<TRequest> EncryptRequestSensitivePropertyUsePublicKeyManagerAsync<TRequest>(IPublicKeyManagerAsync manager, string signScheme, TRequest request, CancellationToken cancellationToken = default)
where TRequest : WechatTenpayRequest
{ {
switch (algorithm) if (manager is null)
throw new NullReferenceException("The platform public key manager is not configured.");
PublicKeyEntry? entry = await manager.GetEntryAsync(request.WechatpaySerialNumber!, cancellationToken).ConfigureAwait(false);
if (!entry.HasValue)
throw new WechatTenpayException($"Failed to encrypt request, because the platform public key manager does not contain a key matched the serial number \"{request.WechatpaySerialNumber}\".");
return PopulateRequestEncryptedFieldsByPublicKey(signScheme, entry.Value.PublicKey, ref request);
}
private static TRequest PopulateRequestEncryptedFieldsByCertificate<TRequest>(string scheme, string certificate, ref TRequest request)
where TRequest : WechatTenpayRequest
{
switch (scheme)
{ {
case EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1: case SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256:
case EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
{ {
string publicKey = RSAUtility.ExportPublicKeyFromCertificate(certificate); string publicKey = RSAUtility.ExportPublicKeyFromCertificate(certificate);
return GenerateEncryptedValueByPublicKey(algorithm, publicKey, value); return PopulateRequestEncryptedFieldsByPublicKey(scheme, publicKey, ref request);
} }
case EncryptionAlgorithms.SM2_C1C3C2_ASN1: case SignSchemes.WECHATPAY2_SM2_WITH_SM3:
{ {
string publicKey = SM2Utility.ExportPublicKeyFromCertificate(certificate); string publicKey = SM2Utility.ExportPublicKeyFromCertificate(certificate);
return GenerateEncryptedValueByPublicKey(algorithm, publicKey, value); return PopulateRequestEncryptedFieldsByPublicKey(scheme, publicKey, ref request);
} }
default: default:
throw new WechatTenpayException($"Failed to encrypt request. Unsupported encryption algorithm: \"{algorithm}\"."); throw new NotSupportedException($"Unsupported signing scheme: \"{scheme}\".");
} }
} }
private static string GenerateEncryptedValueByPublicKey(string algorithm, string publicKey, string value) private static TRequest PopulateRequestEncryptedFieldsByPublicKey<TRequest>(string scheme, string publicKey, ref TRequest request)
where TRequest : WechatTenpayRequest
{ {
switch (algorithm) ReflectionHelper.ReplaceObjectStringProperties(ref request, (_, currentProp, oldValue) =>
{ {
case EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1: if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute)))
return RSAUtility.EncryptWithECB( return (false, oldValue);
publicKeyPem: publicKey,
plainData: value,
paddingMode: RSAUtility.PADDING_MODE_OAEPWITHSHA1ANDMGF1
)!;
case EncryptionAlgorithms.RSA_2048_ECB_PKCS1: WechatTenpaySensitivePropertyAttribute? attribute = currentProp
return RSAUtility.EncryptWithECB( .GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
publicKeyPem: publicKey, .FirstOrDefault(attr => attr.Scheme == scheme);
plainData: value, if (attribute is null)
paddingMode: RSAUtility.PADDING_MODE_PKCS1 return (false, oldValue);
)!;
case EncryptionAlgorithms.SM2_C1C3C2_ASN1: string newValue;
return SM2Utility.Encrypt( switch (attribute.Algorithm)
publicKeyPem: publicKey, {
plainData: value, case EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
asn1Encoding: true newValue = RSAUtility.EncryptWithECB(
)!; publicKeyPem: publicKey,
plainData: oldValue,
paddingMode: RSAUtility.PADDING_MODE_OAEPWITHSHA1ANDMGF1
)!;
break;
default: case EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
throw new WechatTenpayException($"Failed to encrypt request. Unsupported encryption algorithm: \"{algorithm}\"."); newValue = RSAUtility.EncryptWithECB(
} publicKeyPem: publicKey,
plainData: oldValue,
paddingMode: RSAUtility.PADDING_MODE_PKCS1
)!;
break;
case EncryptionAlgorithms.SM2_C1C3C2_ASN1:
newValue = SM2Utility.Encrypt(
publicKeyPem: publicKey,
plainData: oldValue,
asn1Encoding: true
)!;
break;
default:
throw new NotSupportedException($"Unsupported encryption algorithm: \"{attribute.Algorithm}\".");
}
return (true, newValue);
});
return request;
} }
} }
} }

View File

@@ -107,7 +107,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
string signScheme = client.Credentials.SignScheme; string signScheme = client.Credentials.SignScheme;
ReflectionHelper.ReplaceObjectStringProperties(response, (_, currentProp, oldValue) => ReflectionHelper.ReplaceObjectStringProperties(ref response, (_, currentProp, oldValue) =>
{ {
if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute))) if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute)))
return (false, oldValue); return (false, oldValue);

View File

@@ -93,7 +93,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{ {
if (client is null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
return WechatTenpayClientSigningExtensions.VerifySignature( return WechatTenpayClientSigningExtensions._VerifySignature(
client, client,
strTimestamp: responseTimestamp, strTimestamp: responseTimestamp,
strNonce: responseNonce, strNonce: responseNonce,
@@ -192,7 +192,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{ {
if (client is null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
return WechatTenpayClientSigningExtensions.VerifySignatureAsync( return WechatTenpayClientSigningExtensions._VerifySignatureAsync(
client, client,
strTimestamp: responseTimestamp, strTimestamp: responseTimestamp,
strNonce: responseNonce, strNonce: responseNonce,

View File

@@ -0,0 +1,177 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants;
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
internal static class WechatTenpayClientRequestSerialNumberExtensions
{
public static TRequest _EnsureRequestWechatpaySerialNumberIsSet<TRequest>(this WechatTenpayClient client, TRequest request)
where TRequest : WechatTenpayRequest
{
if (client is null) throw new ArgumentNullException(nameof(client));
if (request is null) throw new ArgumentNullException(nameof(request));
try
{
switch (client.PlatformAuthScheme)
{
case PlatformAuthScheme.Certificate:
return EnsureRequestWechatpaySerialNumberIsSetUseCertificateManager<TRequest>(client.PlatformCertificateManager, client.Credentials.SignScheme, request);
case PlatformAuthScheme.PublicKey:
return EnsureRequestWechatpaySerialNumberIsSetUsePublicKeyManager<TRequest>(client.PlatformPublicKeyManager, client.Credentials.SignScheme, request);
default:
throw new NotSupportedException($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\".");
}
}
catch (WechatTenpayException)
{
throw;
}
catch (Exception ex)
{
throw new WechatTenpayException("Failed to set the wechatpay serial number of request. Please see the inner exception for more details.", ex);
}
}
public static async Task<TRequest> _EnsureRequestWechatpaySerialNumberIsSetAsync<TRequest>(this WechatTenpayClient client, TRequest request, CancellationToken cancellationToken = default)
where TRequest : WechatTenpayRequest
{
if (client is null) throw new ArgumentNullException(nameof(client));
if (request is null) throw new ArgumentNullException(nameof(request));
try
{
switch (client.PlatformAuthScheme)
{
case PlatformAuthScheme.Certificate:
{
if (client.PlatformCertificateManager is not ICertificateManagerAsync)
{
// 降级为同步调用
return EnsureRequestWechatpaySerialNumberIsSetUseCertificateManager(client.PlatformCertificateManager, client.Credentials.SignScheme, request);
}
return await EnsureRequestWechatpaySerialNumberIsSetUseCertificateManagerAsync<TRequest>((ICertificateManagerAsync)client.PlatformCertificateManager, client.Credentials.SignScheme, request).ConfigureAwait(false);
}
case PlatformAuthScheme.PublicKey:
{
if (client.PlatformPublicKeyManager is not IPublicKeyManagerAsync)
{
// 降级为同步调用
return EnsureRequestWechatpaySerialNumberIsSetUsePublicKeyManager(client.PlatformPublicKeyManager, client.Credentials.SignScheme, request);
}
return await EnsureRequestWechatpaySerialNumberIsSetUsePublicKeyManagerAsync<TRequest>((IPublicKeyManagerAsync)client.PlatformPublicKeyManager, client.Credentials.SignScheme, request).ConfigureAwait(false);
}
default:
throw new NotSupportedException($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\".");
}
}
catch (WechatTenpayException)
{
throw;
}
catch (Exception ex)
{
throw new WechatTenpayException("Failed to set the wechatpay serial number of request. Please see the inner exception for more details.", ex);
}
}
private static bool TryGetAlgorithmType(string signScheme, out string algorithmType)
{
algorithmType = // 签名方式与加密算法保持一致RSA_SHA256 签名需 RSA 加密SM3 签名需 SM2 加密
SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_RSA :
SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_SM2 :
default!;
return !string.IsNullOrEmpty(algorithmType);
}
private static TRequest EnsureRequestWechatpaySerialNumberIsSetUseCertificateManager<TRequest>(ICertificateManager manager, string signScheme, TRequest request)
where TRequest : WechatTenpayRequest
{
if (string.IsNullOrEmpty(request.WechatpaySerialNumber))
{
if (!TryGetAlgorithmType(signScheme, out string algorithmType))
throw new NotSupportedException($"Unsupported signing scheme: \"{signScheme}\".");
IEnumerable<CertificateEntry> entries = manager.AllEntries();
entries = entries.Where(e => e.AlgorithmType == algorithmType).OrderByDescending(e => e.ExpireTime);
if (!entries.Any())
throw new Exception("The platform certificate manager is empty. Please make sure you have downloaded platform (NOT merchant) certificates first.");
CertificateEntry entry = entries.First();
request.WechatpaySerialNumber = entry.SerialNumber;
}
return request;
}
private static async Task<TRequest> EnsureRequestWechatpaySerialNumberIsSetUseCertificateManagerAsync<TRequest>(ICertificateManagerAsync manager, string signScheme, TRequest request, CancellationToken cancellationToken = default)
where TRequest : WechatTenpayRequest
{
if (string.IsNullOrEmpty(request.WechatpaySerialNumber))
{
if (!TryGetAlgorithmType(signScheme, out string algorithmType))
throw new NotSupportedException($"Unsupported signing scheme: \"{signScheme}\".");
IEnumerable<CertificateEntry> entries = await manager.AllEntriesAsync(cancellationToken).ConfigureAwait(false);
entries = entries.Where(e => e.AlgorithmType == algorithmType).OrderByDescending(e => e.ExpireTime);
if (!entries.Any())
throw new Exception("The platform certificate manager is empty. Please make sure you have downloaded platform (NOT merchant) certificates first.");
CertificateEntry entry = entries.First();
request.WechatpaySerialNumber = entry.SerialNumber;
}
return request;
}
private static TRequest EnsureRequestWechatpaySerialNumberIsSetUsePublicKeyManager<TRequest>(IPublicKeyManager manager, string signScheme, TRequest request)
where TRequest : WechatTenpayRequest
{
if (string.IsNullOrEmpty(request.WechatpaySerialNumber))
{
if (!TryGetAlgorithmType(signScheme, out string algorithmType))
throw new NotSupportedException($"Unsupported signing scheme: \"{signScheme}\".");
IEnumerable<PublicKeyEntry> entries = manager.AllEntries();
entries = entries.Where(e => e.AlgorithmType == algorithmType);
if (!entries.Any())
throw new Exception("The platform public key manager is empty. Perhaps you forget to add one?");
PublicKeyEntry entry = entries.First();
request.WechatpaySerialNumber = entry.SerialNumber;
}
return request;
}
private static async Task<TRequest> EnsureRequestWechatpaySerialNumberIsSetUsePublicKeyManagerAsync<TRequest>(IPublicKeyManagerAsync manager, string signScheme, TRequest request, CancellationToken cancellationToken = default)
where TRequest : WechatTenpayRequest
{
if (string.IsNullOrEmpty(request.WechatpaySerialNumber))
{
if (!TryGetAlgorithmType(signScheme, out string algorithmType))
throw new NotSupportedException($"Unsupported signing scheme: \"{signScheme}\".");
IEnumerable<PublicKeyEntry> entries = await manager.AllEntriesAsync(cancellationToken).ConfigureAwait(false);
entries = entries.Where(e => e.AlgorithmType == algorithmType);
if (!entries.Any())
throw new Exception("The platform public key manager is empty. Perhaps you forget to add one?");
PublicKeyEntry entry = entries.First();
request.WechatpaySerialNumber = entry.SerialNumber;
}
return request;
}
}
}

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -10,75 +11,54 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
internal static class WechatTenpayClientSigningExtensions internal static class WechatTenpayClientSigningExtensions
{ {
public static ErroredResult VerifySignature(this WechatTenpayClient client, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignScheme, string strSerialNumber) public static ErroredResult _VerifySignature(this WechatTenpayClient client, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignScheme, string strSerialNumber)
{ {
if (client is null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
switch (client.PlatformAuthScheme) PlatformAuthScheme authSchema;
if (!TryGetAuthScheme(client, strSerialNumber, out authSchema))
{
return ErroredResult.Fail(new Exception("Could not detect the platform auth schema because the serial number is missing or invalid."));
}
switch (authSchema)
{ {
case PlatformAuthScheme.Certificate: case PlatformAuthScheme.Certificate:
{ {
CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(strSerialNumber); return VerifySignatureUseCertificateManager(client.PlatformCertificateManager, strTimestamp: strTimestamp, strNonce: strNonce, strContent: strContent, strSignature: strSignature, strSignScheme: strSignScheme, strSerialNumber: strSerialNumber);
if (!entry.HasValue)
{
return ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate matched the serial number \"{strSerialNumber}\"."));
}
return GenerateSignatureResultByCertificate(
scheme: strSignScheme,
certificate: entry.Value.Certificate,
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
signature: strSignature
);
} }
case PlatformAuthScheme.PublicKey: case PlatformAuthScheme.PublicKey:
{ {
PublicKeyEntry? entry = client.PlatformPublicKeyManager.GetEntry(strSerialNumber); return VerifySignatureUsePublicKeyManager(client.PlatformPublicKeyManager, strTimestamp: strTimestamp, strNonce: strNonce, strContent: strContent, strSignature: strSignature, strSignScheme: strSignScheme, strSerialNumber: strSerialNumber);
if (!entry.HasValue)
{
return ErroredResult.Fail(new Exception($"The platform public key manager does not contain a key matched the serial number \"{strSerialNumber}\"."));
}
return GenerateSignatureResultByPublicKey(
scheme: strSignScheme,
publicKey: entry.Value.PublicKey,
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
signature: strSignature
);
} }
default: default:
return ErroredResult.Fail(new Exception($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\".")); return ErroredResult.Fail(new NotSupportedException($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\"."));
} }
} }
public static async Task<ErroredResult> VerifySignatureAsync(this WechatTenpayClient client, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignScheme, string strSerialNumber, CancellationToken cancellationToken = default) public static async Task<ErroredResult> _VerifySignatureAsync(this WechatTenpayClient client, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignScheme, string strSerialNumber, CancellationToken cancellationToken = default)
{ {
if (client is null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
switch (client.PlatformAuthScheme) PlatformAuthScheme authSchema;
if (!TryGetAuthScheme(client, strSerialNumber, out authSchema))
{
return ErroredResult.Fail(new Exception("Could not detect the platform auth schema because the serial number is missing or invalid."));
}
switch (authSchema)
{ {
case PlatformAuthScheme.Certificate: case PlatformAuthScheme.Certificate:
{ {
if (client.PlatformCertificateManager is not ICertificateManagerAsync) if (client.PlatformCertificateManager is not ICertificateManagerAsync)
{ {
// 降级为同步调用 // 降级为同步调用
return VerifySignature(client, strTimestamp, strNonce, strContent, strSignature, strSignScheme, strSerialNumber); return _VerifySignature(client, strTimestamp, strNonce, strContent, strSignature, strSignScheme, strSerialNumber);
} }
CertificateEntry? entry = await ((ICertificateManagerAsync)client.PlatformCertificateManager).GetEntryAsync(strSerialNumber, cancellationToken).ConfigureAwait(false); return await VerifySignatureUseCertificateManagerAsync((ICertificateManagerAsync)client.PlatformCertificateManager, strTimestamp: strTimestamp, strNonce: strNonce, strContent: strContent, strSignature: strSignature, strSignScheme: strSignScheme, strSerialNumber: strSerialNumber, cancellationToken).ConfigureAwait(false);
if (!entry.HasValue)
{
return ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate matched the serial number \"{strSerialNumber}\"."));
}
return GenerateSignatureResultByCertificate(
scheme: strSignScheme,
certificate: entry.Value.Certificate,
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
signature: strSignature
);
} }
case PlatformAuthScheme.PublicKey: case PlatformAuthScheme.PublicKey:
@@ -86,34 +66,130 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
if (client.PlatformCertificateManager is not IPublicKeyManagerAsync) if (client.PlatformCertificateManager is not IPublicKeyManagerAsync)
{ {
// 降级为同步调用 // 降级为同步调用
return VerifySignature(client, strTimestamp, strNonce, strContent, strSignature, strSignScheme, strSerialNumber); return _VerifySignature(client, strTimestamp, strNonce, strContent, strSignature, strSignScheme, strSerialNumber);
} }
PublicKeyEntry? entry = await ((IPublicKeyManagerAsync)client.PlatformPublicKeyManager).GetEntryAsync(strSerialNumber, cancellationToken).ConfigureAwait(false); return await VerifySignatureUseCertificateManagerAsync((IPublicKeyManagerAsync)client.PlatformPublicKeyManager, strTimestamp: strTimestamp, strNonce: strNonce, strContent: strContent, strSignature: strSignature, strSignScheme: strSignScheme, strSerialNumber: strSerialNumber, cancellationToken).ConfigureAwait(false);
if (!entry.HasValue)
{
return ErroredResult.Fail(new Exception($"The platform public key manager does not contain a key matched the serial number \"{strSerialNumber}\"."));
}
return GenerateSignatureResultByPublicKey(
scheme: strSignScheme,
publicKey: entry.Value.PublicKey,
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
signature: strSignature
);
} }
default: default:
return ErroredResult.Fail(new Exception($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\".")); return ErroredResult.Fail(new NotSupportedException($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\"."));
} }
} }
private static ErroredResult VerifySignatureUseCertificateManager(ICertificateManager manager, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignScheme, string strSerialNumber)
{
if (manager is null)
{
return ErroredResult.Fail(new NullReferenceException("The platform certificate manager is not configured."));
}
CertificateEntry? entry = manager.GetEntry(strSerialNumber);
if (!entry.HasValue)
{
return ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate matched the serial number \"{strSerialNumber}\"."));
}
return GenerateVerifyResultByCertificate(
scheme: strSignScheme,
certificate: entry.Value.Certificate,
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
signature: strSignature
);
}
private static async Task<ErroredResult> VerifySignatureUseCertificateManagerAsync(ICertificateManagerAsync manager, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignScheme, string strSerialNumber, CancellationToken cancellationToken = default)
{
if (manager is null)
{
return ErroredResult.Fail(new NullReferenceException("The platform certificate manager is not configured."));
}
CertificateEntry? entry = await manager.GetEntryAsync(strSerialNumber, cancellationToken).ConfigureAwait(false);
if (!entry.HasValue)
{
return ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate matched the serial number \"{strSerialNumber}\"."));
}
return GenerateVerifyResultByCertificate(
scheme: strSignScheme,
certificate: entry.Value.Certificate,
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
signature: strSignature
);
}
private static ErroredResult VerifySignatureUsePublicKeyManager(IPublicKeyManager manager, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignScheme, string strSerialNumber)
{
if (manager is null)
{
return ErroredResult.Fail(new NullReferenceException("The platform public key manager is not configured."));
}
PublicKeyEntry? entry = manager.GetEntry(strSerialNumber);
if (!entry.HasValue)
{
return ErroredResult.Fail(new Exception($"The platform public key manager does not contain a key matched the serial number \"{strSerialNumber}\"."));
}
return GenerateVerifyResultByPublicKey(
scheme: strSignScheme,
publicKey: entry.Value.PublicKey,
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
signature: strSignature
);
}
private static async Task<ErroredResult> VerifySignatureUseCertificateManagerAsync(IPublicKeyManagerAsync manager, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignScheme, string strSerialNumber, CancellationToken cancellationToken = default)
{
if (manager is null)
{
return ErroredResult.Fail(new NullReferenceException("The platform public key manager is not configured."));
}
PublicKeyEntry? entry = await manager.GetEntryAsync(strSerialNumber, cancellationToken).ConfigureAwait(false);
if (!entry.HasValue)
{
return ErroredResult.Fail(new Exception($"The platform public key manager does not contain a key matched the serial number \"{strSerialNumber}\"."));
}
return GenerateVerifyResultByPublicKey(
scheme: strSignScheme,
publicKey: entry.Value.PublicKey,
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
signature: strSignature
);
}
private static bool TryGetAuthScheme(WechatTenpayClient client, string strSerialNumber, out PlatformAuthScheme authScheme)
{
authScheme = client.PlatformAuthScheme;
if (client.PlatformAuthFallbackSwitch)
{
if (string.IsNullOrEmpty(strSerialNumber))
{
return false;
}
if (Regex.IsMatch(strSerialNumber, "^PUB_KEY_ID_", RegexOptions.IgnoreCase))
{
authScheme = PlatformAuthScheme.PublicKey;
}
else if (Regex.IsMatch(strSerialNumber, "^[A-Za-z0-9]+$", RegexOptions.IgnoreCase))
{
authScheme = PlatformAuthScheme.Certificate;
}
}
return true;
}
private static string GenerateMessageForSignature(string timestamp, string nonce, string body) private static string GenerateMessageForSignature(string timestamp, string nonce, string body)
{ {
return $"{timestamp}\n{nonce}\n{body}\n"; return $"{timestamp}\n{nonce}\n{body}\n";
} }
private static ErroredResult GenerateSignatureResultByCertificate(string scheme, string certificate, string message, string signature) private static ErroredResult GenerateVerifyResultByCertificate(string scheme, string certificate, string message, string signature)
{ {
string publicKey = string.Empty; string publicKey = string.Empty;
@@ -146,10 +222,10 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
break; break;
} }
return GenerateSignatureResultByPublicKey(scheme, publicKey, message, signature); return GenerateVerifyResultByPublicKey(scheme, publicKey, message, signature);
} }
private static ErroredResult GenerateSignatureResultByPublicKey(string scheme, string publicKey, string message, string signature) private static ErroredResult GenerateVerifyResultByPublicKey(string scheme, string publicKey, string message, string signature)
{ {
ErroredResult result; ErroredResult result;
@@ -199,7 +275,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
default: default:
{ {
result = ErroredResult.Fail(new Exception($"Unsupported signing scheme: \"{scheme}\".")); result = ErroredResult.Fail(new NotSupportedException($"Unsupported signing scheme: \"{scheme}\"."));
} }
break; break;
} }

View File

@@ -10,7 +10,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
{ {
internal static partial class ReflectionHelper internal static partial class ReflectionHelper
{ {
public static void ReplaceObjectStringProperties(object targetObj, ReplaceObjectStringPropertiesReplacementDelegate replacement) public static void ReplaceObjectStringProperties<T>(ref T targetObj, ReplaceObjectStringPropertiesReplacementDelegate replacement)
{ {
if (targetObj is null) throw new ArgumentNullException(nameof(targetObj)); if (targetObj is null) throw new ArgumentNullException(nameof(targetObj));
if (replacement is null) throw new ArgumentNullException(nameof(replacement)); if (replacement is null) throw new ArgumentNullException(nameof(replacement));
@@ -24,7 +24,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
{ {
public delegate (bool IsModified, string NewValue) ReplaceObjectStringPropertiesReplacementDelegate(object currentObj, PropertyInfo? currentProp, string oldValue); public delegate (bool IsModified, string NewValue) ReplaceObjectStringPropertiesReplacementDelegate(object currentObj, PropertyInfo? currentProp, string oldValue);
private static void InnerReplaceObjectStringProperties(ref object currentObj, PropertyInfo? currentProp, ReplaceObjectStringPropertiesReplacementDelegate replacement, ISet<object> visited) private static void InnerReplaceObjectStringProperties<T>(ref T currentObj, PropertyInfo? currentProp, ReplaceObjectStringPropertiesReplacementDelegate replacement, ISet<object> visited)
{ {
if (currentObj is null) throw new ArgumentNullException(nameof(currentObj)); if (currentObj is null) throw new ArgumentNullException(nameof(currentObj));
if (replacement is null) throw new ArgumentNullException(nameof(replacement)); if (replacement is null) throw new ArgumentNullException(nameof(replacement));
@@ -53,7 +53,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
// 处理数组类型 // 处理数组类型
if (type.IsArray) if (type.IsArray)
{ {
Array currentObjAsArray = (Array)currentObj; Array currentObjAsArray = (currentObj as Array)!;
for (int i = 0; i < currentObjAsArray.Length; i++) for (int i = 0; i < currentObjAsArray.Length; i++)
{ {
object? element = currentObjAsArray.GetValue(i); object? element = currentObjAsArray.GetValue(i);

View File

@@ -20,7 +20,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
public Settings.Credentials Credentials { get; } public Settings.Credentials Credentials { get; }
/// <summary> /// <summary>
/// 获取当前客户端使用的微信支付平台认证方案 /// 获取当前客户端是否开启微信支付平台 API 认证方案回退开关
/// </summary>
public bool PlatformAuthFallbackSwitch { get; }
/// <summary>
/// 获取当前客户端使用的微信支付平台 API 认证方案。
/// </summary> /// </summary>
public Settings.PlatformAuthScheme PlatformAuthScheme { get; } public Settings.PlatformAuthScheme PlatformAuthScheme { get; }
@@ -65,6 +70,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
if (options is null) throw new ArgumentNullException(nameof(options)); if (options is null) throw new ArgumentNullException(nameof(options));
Credentials = new Settings.Credentials(options); Credentials = new Settings.Credentials(options);
PlatformAuthFallbackSwitch = options.PlatformAuthFallbackSwitch;
PlatformAuthScheme = options.PlatformAuthScheme; PlatformAuthScheme = options.PlatformAuthScheme;
PlatformCertificateManager = options.PlatformCertificateManager; PlatformCertificateManager = options.PlatformCertificateManager;
PlatformPublicKeyManager = options.PlatformPublicKeyManager; PlatformPublicKeyManager = options.PlatformPublicKeyManager;
@@ -96,6 +102,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{ {
IFlurlRequest flurlRequest = base.CreateFlurlRequest(request, httpMethod, urlSegments); IFlurlRequest flurlRequest = base.CreateFlurlRequest(request, httpMethod, urlSegments);
if (PlatformAuthFallbackSwitch)
{
this._EnsureRequestWechatpaySerialNumberIsSet(request);
}
if (AutoEncryptRequestSensitiveProperty) if (AutoEncryptRequestSensitiveProperty)
{ {
this.EncryptRequestSensitiveProperty(request); this.EncryptRequestSensitiveProperty(request);

View File

@@ -74,7 +74,17 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
public bool AutoDecryptResponseSensitiveProperty { get; set; } public bool AutoDecryptResponseSensitiveProperty { get; set; }
/// <summary> /// <summary>
/// 获取或设置微信支付平台认证方案。 /// 获取或设置微信支付平台 API 认证方案回退开关。<br/>
/// 开启后会在验证签名时根据响应或回调中接收到的序列号自动推测使用哪种认证方案。<br/>
/// 开启后会有一定的性能损耗,建议仅在灰度切换期间时开启,待切换进度为百分之百后请关闭。
/// <para>
/// 默认值false
/// </para>
/// </summary>
public bool PlatformAuthFallbackSwitch { get; set; }
/// <summary>
/// 获取或设置微信支付平台 API 认证方案。
/// <para>默认值:<see cref="Settings.PlatformAuthScheme.Certificate"/></para> /// <para>默认值:<see cref="Settings.PlatformAuthScheme.Certificate"/></para>
/// </summary> /// </summary>
public Settings.PlatformAuthScheme PlatformAuthScheme { get; set; } = Settings.PlatformAuthScheme.Certificate; public Settings.PlatformAuthScheme PlatformAuthScheme { get; set; } = Settings.PlatformAuthScheme.Certificate;
@@ -82,7 +92,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
/// <summary> /// <summary>
/// 获取或设置微信支付平台证书管理器。 /// 获取或设置微信支付平台证书管理器。
/// <para> /// <para>
/// 仅当 <see cref="PlatformAuthScheme"/> 的值为 <see cref="Settings.PlatformAuthScheme.Certificate"/> 时有效。 /// 仅当 <see cref="PlatformAuthScheme"/> 的值为 <see cref="Settings.PlatformAuthScheme.Certificate"/>,或开启 <see cref="PlatformAuthSchemaFallback"/> 开关时有效。
/// </para> /// </para>
/// <para> /// <para>
/// 默认值:<see cref="Settings.InMemoryCertificateManager"/> /// 默认值:<see cref="Settings.InMemoryCertificateManager"/>
@@ -93,7 +103,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
/// <summary> /// <summary>
/// 获取或设置微信支付平台公钥管理器。 /// 获取或设置微信支付平台公钥管理器。
/// <para> /// <para>
/// 仅当 <see cref="PlatformAuthScheme"/> 的值为 <see cref="Settings.PlatformAuthScheme.PublicKey"/> 时有效。 /// 仅当 <see cref="PlatformAuthScheme"/> 的值为 <see cref="Settings.PlatformAuthScheme.PublicKey"/>,或开启 <see cref="PlatformAuthSchemaFallback"/> 开关时有效。
/// </para> /// </para>
/// <para> /// <para>
/// 默认值:<see cref="Settings.InMemoryPublicKeyManager"/> /// 默认值:<see cref="Settings.InMemoryPublicKeyManager"/>

View File

@@ -2561,7 +2561,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
var manager = new Settings.InMemoryCertificateManager(); var manager = new Settings.InMemoryCertificateManager();
manager.AddEntry(new Settings.CertificateEntry( manager.AddEntry(new Settings.CertificateEntry(
algorithmType: Settings.CertificateEntry.ALGORITHM_TYPE_RSA, algorithmType: Settings.CertificateEntry.ALGORITHM_TYPE_RSA,
serialNumber: "OBSOLETED1", serialNumber: "MOCK1",
certificate: "-----BEGIN CERTIFICATE----------END CERTIFICATE-----", certificate: "-----BEGIN CERTIFICATE----------END CERTIFICATE-----",
effectiveTime: DateTimeOffset.Now.AddSeconds(-1), effectiveTime: DateTimeOffset.Now.AddSeconds(-1),
expireTime: DateTimeOffset.Now.AddSeconds(-1) expireTime: DateTimeOffset.Now.AddSeconds(-1)
@@ -2575,7 +2575,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
)); ));
manager.AddEntry(new Settings.CertificateEntry( manager.AddEntry(new Settings.CertificateEntry(
algorithmType: Settings.CertificateEntry.ALGORITHM_TYPE_RSA, algorithmType: Settings.CertificateEntry.ALGORITHM_TYPE_RSA,
serialNumber: "OBSOLETED2", serialNumber: "MOCK2",
certificate: "-----BEGIN CERTIFICATE----------END CERTIFICATE-----", certificate: "-----BEGIN CERTIFICATE----------END CERTIFICATE-----",
effectiveTime: DateTimeOffset.Now.AddSeconds(-1), effectiveTime: DateTimeOffset.Now.AddSeconds(-1),
expireTime: DateTimeOffset.Now.AddSeconds(-1) expireTime: DateTimeOffset.Now.AddSeconds(-1)
@@ -2598,7 +2598,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
var manager = new Settings.InMemoryCertificateManager(); var manager = new Settings.InMemoryCertificateManager();
manager.AddEntry(new Settings.CertificateEntry( manager.AddEntry(new Settings.CertificateEntry(
algorithmType: Settings.CertificateEntry.ALGORITHM_TYPE_SM2, algorithmType: Settings.CertificateEntry.ALGORITHM_TYPE_SM2,
serialNumber: "OBSOLETED1", serialNumber: "MOCK1",
certificate: "-----BEGIN CERTIFICATE----------END CERTIFICATE-----", certificate: "-----BEGIN CERTIFICATE----------END CERTIFICATE-----",
effectiveTime: DateTimeOffset.Now.AddSeconds(-1), effectiveTime: DateTimeOffset.Now.AddSeconds(-1),
expireTime: DateTimeOffset.Now.AddSeconds(-1) expireTime: DateTimeOffset.Now.AddSeconds(-1)
@@ -2612,7 +2612,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
)); ));
manager.AddEntry(new Settings.CertificateEntry( manager.AddEntry(new Settings.CertificateEntry(
algorithmType: Settings.CertificateEntry.ALGORITHM_TYPE_SM2, algorithmType: Settings.CertificateEntry.ALGORITHM_TYPE_SM2,
serialNumber: "OBSOLETED2", serialNumber: "MOCK2",
certificate: "-----BEGIN CERTIFICATE----------END CERTIFICATE-----", certificate: "-----BEGIN CERTIFICATE----------END CERTIFICATE-----",
effectiveTime: DateTimeOffset.Now.AddSeconds(-1), effectiveTime: DateTimeOffset.Now.AddSeconds(-1),
expireTime: DateTimeOffset.Now.AddSeconds(-1) expireTime: DateTimeOffset.Now.AddSeconds(-1)