diff --git a/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net6/Services/HttpClients/IWechatTenpayCertificateManagerFactory.cs b/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net6/Services/HttpClients/IWechatTenpayCertificateManagerFactory.cs index 300d6f4f..516dec90 100644 --- a/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net6/Services/HttpClients/IWechatTenpayCertificateManagerFactory.cs +++ b/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net6/Services/HttpClients/IWechatTenpayCertificateManagerFactory.cs @@ -1,9 +1,9 @@ -namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients { using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings; public interface IWechatTenpayCertificateManagerFactory { - CertificateManager Create(string merchantId); + ICertificateManager Create(string merchantId); } } diff --git a/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net6/Services/HttpClients/Implements/WechatTenpayCertificateManagerFactory.cs b/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net6/Services/HttpClients/Implements/WechatTenpayCertificateManagerFactory.cs index 3cf2aac9..442ef33f 100644 --- a/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net6/Services/HttpClients/Implements/WechatTenpayCertificateManagerFactory.cs +++ b/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_Net6/Services/HttpClients/Implements/WechatTenpayCertificateManagerFactory.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients.Implements { @@ -6,14 +6,14 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients.Imple internal partial class WechatTenpayCertificateManagerFactory : IWechatTenpayCertificateManagerFactory { - private readonly ConcurrentDictionary _dict; + private readonly ConcurrentDictionary _dict; public WechatTenpayCertificateManagerFactory() { - _dict = new ConcurrentDictionary(); + _dict = new ConcurrentDictionary(); } - public CertificateManager Create(string merchantId) + public ICertificateManager Create(string merchantId) { // NOTICE: // 这里的工厂方法是为了演示多租户而存在的,可根据商户号生成不同的证书管理器。 diff --git a/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_NetFramework47/Services/HttpClients/IWechatTenpayCertificateManagerFactory.cs b/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_NetFramework47/Services/HttpClients/IWechatTenpayCertificateManagerFactory.cs index 9c7cd91d..516dec90 100644 --- a/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_NetFramework47/Services/HttpClients/IWechatTenpayCertificateManagerFactory.cs +++ b/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_NetFramework47/Services/HttpClients/IWechatTenpayCertificateManagerFactory.cs @@ -1,7 +1,9 @@ -namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients { + using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings; + public interface IWechatTenpayCertificateManagerFactory { - CertificateManager Create(string merchantId); + ICertificateManager Create(string merchantId); } } diff --git a/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_NetFramework47/Services/HttpClients/Implements/WechatTenpayCertificateManagerFactory.cs b/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_NetFramework47/Services/HttpClients/Implements/WechatTenpayCertificateManagerFactory.cs index 73c08d60..bd45336c 100644 --- a/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_NetFramework47/Services/HttpClients/Implements/WechatTenpayCertificateManagerFactory.cs +++ b/samples/SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample_NetFramework47/Services/HttpClients/Implements/WechatTenpayCertificateManagerFactory.cs @@ -1,12 +1,16 @@ -namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients.Implements +using System.Collections.Concurrent; + +namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients.Implements { + using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings; + internal partial class WechatTenpayCertificateManagerFactory : IWechatTenpayCertificateManagerFactory { - private readonly ConcurrentDictionary _dict; + private readonly ConcurrentDictionary _dict; public WechatTenpayCertificateManagerFactory() { - _dict = new ConcurrentDictionary(); + _dict = new ConcurrentDictionary(); } public CertificateManager Create(string merchantId) diff --git a/src/SKIT.FlurlHttpClient.Wechat.Ads/Settings/Credentials.cs b/src/SKIT.FlurlHttpClient.Wechat.Ads/Settings/Credentials.cs index 719c2205..1b9109fa 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.Ads/Settings/Credentials.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.Ads/Settings/Credentials.cs @@ -2,7 +2,7 @@ using System; namespace SKIT.FlurlHttpClient.Wechat.Ads.Settings { - public class Credentials + public sealed class Credentials { /// /// 初始化客户端时 的副本。 diff --git a/src/SKIT.FlurlHttpClient.Wechat.Api/Settings/Credentials.cs b/src/SKIT.FlurlHttpClient.Wechat.Api/Settings/Credentials.cs index b190369b..46a665b1 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.Api/Settings/Credentials.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.Api/Settings/Credentials.cs @@ -2,7 +2,7 @@ using System; namespace SKIT.FlurlHttpClient.Wechat.Api.Settings { - public class Credentials + public sealed class Credentials { /// /// 初始化客户端时 的副本。 diff --git a/src/SKIT.FlurlHttpClient.Wechat.OpenAI/Settings/Credentials.cs b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/Settings/Credentials.cs index 27d8e2af..e0ff3a0c 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.OpenAI/Settings/Credentials.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.OpenAI/Settings/Credentials.cs @@ -1,8 +1,8 @@ -using System; +using System; namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Settings { - public class Credentials + public sealed class Credentials { /// /// 初始化客户端时 的副本。 diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Settings/Credentials.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Settings/Credentials.cs index 869283f0..f50a61db 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Settings/Credentials.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/Settings/Credentials.cs @@ -2,7 +2,7 @@ using System; namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Settings { - public class Credentials + public sealed class Credentials { /// /// 初始化客户端时 的副本。 diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV2/Settings/Credentials.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV2/Settings/Credentials.cs index 51264b32..c4e63e5b 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV2/Settings/Credentials.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV2/Settings/Credentials.cs @@ -2,7 +2,7 @@ using System; namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Settings { - public class Credentials + public sealed class Credentials { /// /// 初始化客户端时 的副本。 diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV2/Settings/WechatpayHttpHandler.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV2/Settings/WechatpayHttpHandler.cs index 4a0de8b0..e28dea93 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV2/Settings/WechatpayHttpHandler.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV2/Settings/WechatpayHttpHandler.cs @@ -6,7 +6,7 @@ using System.Security.Cryptography.X509Certificates; namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Settings { - public class WechatpayHttpHandler : DelegatingHandler + public sealed class WechatpayHttpHandler : DelegatingHandler { public WechatpayHttpHandler(byte[]? certificateBytes, string? certificatePassword) { diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventDecryptionExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventDecryptionExtensions.cs index ad0837f7..5ea47d15 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventDecryptionExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventDecryptionExtensions.cs @@ -3,6 +3,7 @@ using System; namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 { using SKIT.FlurlHttpClient.Primitives; + using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants; public static class WechatTenpayClientEventDecryptionExtensions { @@ -52,7 +53,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 string plainJson; switch (webhookEventResource.Algorithm) { - case Constants.EncryptionAlgorithms.AEAD_AES_256_GCM: + case EncryptionAlgorithms.AEAD_AES_256_GCM: { try { @@ -70,7 +71,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 } break; - case Constants.EncryptionAlgorithms.AEAD_SM4_128_GCM: + case EncryptionAlgorithms.AEAD_SM4_128_GCM: { try { diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventVerificationExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventVerificationExtensions.cs index fa5e3cd4..6c8eec89 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventVerificationExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventVerificationExtensions.cs @@ -1,8 +1,11 @@ using System; +using System.Threading; +using System.Threading.Tasks; namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 { using SKIT.FlurlHttpClient.Primitives; + using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants; public static class WechatTenpayClientEventVerificationExtensions { @@ -29,7 +32,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 webhookNonce: webhookNonce, webhookBody: webhookBody, webhookSignature: webhookSignature, - webhookSignatureType: Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256, + webhookSignatureType: SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256, webhookSerialNumber: webhookSerialNumber ); } @@ -64,5 +67,66 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 strSerialNumber: webhookSerialNumber ); } + + /// + /// 异步验证回调通知事件签名。 + /// + /// REF:
+ ///
+ /// + ///
+ ///
+ /// + /// 微信回调通知中的 "Wechatpay-Timestamp" 请求标头。 + /// 微信回调通知中的 "Wechatpay-Nonce" 请求标头。 + /// 微信回调通知中请求正文。 + /// 微信回调通知中的 "Wechatpay-Signature" 请求标头。 + /// 微信回调通知中的 "Wechatpay-Serial" 请求标头。 + /// + public static Task VerifyEventSignatureAsync(this WechatTenpayClient client, string webhookTimestamp, string webhookNonce, string webhookBody, string webhookSignature, string webhookSerialNumber, CancellationToken cancellationToken = default) + { + return VerifyEventSignatureAsync( + client, + webhookTimestamp: webhookTimestamp, + webhookNonce: webhookNonce, + webhookBody: webhookBody, + webhookSignature: webhookSignature, + webhookSignatureType: SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256, + webhookSerialNumber: webhookSerialNumber, + cancellationToken: cancellationToken + ); + } + + /// + /// 异步验证回调通知事件签名。 + /// + /// REF:
+ ///
+ /// + ///
+ ///
+ /// + /// 微信回调通知中的 "Wechatpay-Timestamp" 请求标头。 + /// 微信回调通知中的 "Wechatpay-Nonce" 请求标头。 + /// 微信回调通知中请求正文。 + /// 微信回调通知中的 "Wechatpay-Signature" 请求标头。 + /// 微信回调通知中的 "Wechatpay-Signature-Type" 请求标头。 + /// 微信回调通知中的 "Wechatpay-Serial" 请求标头。 + /// + public static Task VerifyEventSignatureAsync(this WechatTenpayClient client, string webhookTimestamp, string webhookNonce, string webhookBody, string webhookSignature, string webhookSignatureType, string webhookSerialNumber, CancellationToken cancellationToken = default) + { + if (client is null) throw new ArgumentNullException(nameof(client)); + + return WechatTenpayClientSigningExtensions.VerifySignatureAsync( + client, + strTimestamp: webhookTimestamp, + strNonce: webhookNonce, + strContent: webhookBody, + strSignature: webhookSignature, + strSignScheme: webhookSignatureType, + strSerialNumber: webhookSerialNumber, + cancellationToken: cancellationToken + ); + } } } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientParameterExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientParameterExtensions.cs index 985dbcbe..f6cceaf7 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientParameterExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientParameterExtensions.cs @@ -4,6 +4,8 @@ using System.Collections.ObjectModel; namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 { + using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants; + public static class WechatTenpayClientParameterExtensions { /// @@ -47,7 +49,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 { "timeStamp", timestamp }, { "nonceStr", nonce }, { "package", package }, - { "signType", Constants.SignTypes.RSA }, + { "signType", SignTypes.RSA }, { "paySign", sign } }); } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientRequestEncryptionExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientRequestEncryptionExtensions.cs index bde6a9cb..a6b1a5b8 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientRequestEncryptionExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientRequestEncryptionExtensions.cs @@ -2,9 +2,12 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Threading; +using System.Threading.Tasks; namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 { + using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants; using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings; using SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities; @@ -30,8 +33,94 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 string signScheme = client.Credentials.SignScheme; string algorithmType = // 签名方式与加密算法保持一致:RSA_SHA256 签名需 RSA 加密,SM3 签名需 SM2 加密 - Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_RSA : - Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_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}\"."); + + ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) => + { + if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute))) + return (false, oldValue); + + WechatTenpaySensitivePropertyAttribute? attribute = currentProp + .GetCustomAttributes() + .FirstOrDefault(attr => attr.Scheme == signScheme); + if (attribute is null) + return (false, oldValue); + + string certificate; + if (string.IsNullOrEmpty(request.WechatpayCertificateSerialNumber)) + { + // 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的 + IEnumerable entries = client.PlatformCertificateManager.AllEntries() + .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.WechatpayCertificateSerialNumber = entry.SerialNumber; + } + else + { + // 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值 + CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(request.WechatpayCertificateSerialNumber!); + 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.WechatpayCertificateSerialNumber}\". Please make sure you have downloaded platform (NOT merchant) certificates first."); + } + + certificate = entry.Value.Certificate; + } + + 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); + } + } + + /// + /// 异步加密请求中传入的敏感数据。该方法会改变传入的请求模型对象。 + /// + /// + /// + /// + /// + public static Task EncryptRequestSensitivePropertyAsync(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)); + + if (client.PlatformCertificateManager is not ICertificateManagerAsync) + { + // 降级为同步调用 + return Task.FromResult(request); + } + + try + { + bool requireEncrypt = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute)); + if (!requireEncrypt) + return Task.FromResult(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}\"."); ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) => @@ -52,7 +141,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is not initialized."); // 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的 - IEnumerable entries = client.PlatformCertificateManager.AllEntries() + IEnumerable entries = ((ICertificateManagerAsync)client.PlatformCertificateManager) + .AllEntriesAsync(cancellationToken) + .ConfigureAwait(false) + .GetAwaiter() + .GetResult() .Where(e => e.AlgorithmType == algorithmType) .OrderByDescending(e => e.ExpireTime); if (!entries.Any()) @@ -67,7 +160,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 else { // 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值 - CertificateEntry? entry = client.PlatformCertificateManager?.GetEntry(request.WechatpayCertificateSerialNumber!); + CertificateEntry? entry = ((ICertificateManagerAsync)client.PlatformCertificateManager) + .GetEntryAsync(request.WechatpayCertificateSerialNumber!, cancellationToken) + .ConfigureAwait(false) + .GetAwaiter() + .GetResult(); 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.WechatpayCertificateSerialNumber}\". Please make sure you have downloaded platform (NOT merchant) certificates first."); @@ -76,49 +173,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 certificate = entry.Value.Certificate; } - string newValue; - switch (attribute.Algorithm) - { - case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1: - { - newValue = RSAUtility.EncryptWithECBByCertificate( - certificatePem: certificate, - plainData: oldValue, - paddingMode: RSAUtility.PADDING_MODE_OAEPWITHSHA1ANDMGF1 - )!; - } - break; - - case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS1: - { - newValue = RSAUtility.EncryptWithECBByCertificate( - certificatePem: certificate, - plainData: oldValue, - paddingMode: RSAUtility.PADDING_MODE_PKCS1 - )!; - } - break; - - case Constants.EncryptionAlgorithms.SM2_C1C3C2_ASN1: - { - newValue = SM2Utility.EncryptByCertificate( - certificatePem: certificate, - plainData: oldValue, - asn1Encoding: true - )!; - } - break; - - default: - { - throw new WechatTenpayException($"Failed to encrypt request. Unsupported encryption algorithm: \"{attribute.Algorithm}\"."); - } - } - + string newValue = GenerateEncryptedValueByCertificate(attribute.Algorithm, certificate, oldValue); return (true, newValue); }); - return request; + return Task.FromResult(request); } catch (WechatTenpayException) { @@ -129,5 +188,35 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 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) + { + switch (algorithm) + { + case EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1: + return RSAUtility.EncryptWithECBByCertificate( + certificatePem: certificate, + plainData: value, + paddingMode: RSAUtility.PADDING_MODE_OAEPWITHSHA1ANDMGF1 + )!; + + case EncryptionAlgorithms.RSA_2048_ECB_PKCS1: + return RSAUtility.EncryptWithECBByCertificate( + certificatePem: certificate, + plainData: value, + paddingMode: RSAUtility.PADDING_MODE_PKCS1 + )!; + + case EncryptionAlgorithms.SM2_C1C3C2_ASN1: + return SM2Utility.EncryptByCertificate( + certificatePem: certificate, + plainData: value, + asn1Encoding: true + )!; + + default: + throw new WechatTenpayException($"Failed to encrypt request. Unsupported encryption algorithm: \"{algorithm}\"."); + } + } } } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseDecryptionExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseDecryptionExtensions.cs index 09b8e359..4cb42f13 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseDecryptionExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseDecryptionExtensions.cs @@ -5,6 +5,7 @@ using System.Reflection; namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 { using SKIT.FlurlHttpClient.Primitives; + using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants; using SKIT.FlurlHttpClient.Wechat.TenpayV3.Models; using SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities; @@ -31,7 +32,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 switch (certificate.EncryptCertificate.Algorithm) { - case Constants.EncryptionAlgorithms.AEAD_AES_256_GCM: + case EncryptionAlgorithms.AEAD_AES_256_GCM: { if (string.IsNullOrEmpty(client.Credentials.MerchantCertificatePrivateKey)) throw new WechatTenpayException("Failed to decrypt response, because the merchant private key is not set."); @@ -45,7 +46,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 } break; - case Constants.EncryptionAlgorithms.AEAD_SM4_128_GCM: + case EncryptionAlgorithms.AEAD_SM4_128_GCM: { if (string.IsNullOrEmpty(client.Credentials.MerchantV3Secret)) throw new WechatTenpayException("Failed to decrypt response, because the merchant APIv3 secret is not set."); @@ -120,7 +121,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 string newValue; switch (attribute.Algorithm) { - case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1: + case EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1: { newValue = RSAUtility.DecryptWithECB( privateKeyPem: client.Credentials.MerchantCertificatePrivateKey, @@ -130,7 +131,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 } break; - case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS1: + case EncryptionAlgorithms.RSA_2048_ECB_PKCS1: { newValue = RSAUtility.DecryptWithECB( privateKeyPem: client.Credentials.MerchantCertificatePrivateKey, @@ -140,7 +141,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 } break; - case Constants.EncryptionAlgorithms.SM2_C1C3C2_ASN1: + case EncryptionAlgorithms.SM2_C1C3C2_ASN1: { newValue = SM2Utility.Decrypt( privateKeyPem: client.Credentials.MerchantCertificatePrivateKey, diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseVerificationExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseVerificationExtensions.cs index 41d2f7c3..64b77160 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseVerificationExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseVerificationExtensions.cs @@ -1,9 +1,12 @@ using System; using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 { using SKIT.FlurlHttpClient.Primitives; + using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants; public static class WechatTenpayClientResponseVerificationExtensions { @@ -59,7 +62,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 responseNonce: responseNonce, responseBody: responseBody, responseSignature: responseSignature, - responseSignatureType: Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256, + responseSignatureType: SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256, responseSerialNumber ); } @@ -94,5 +97,96 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 strSerialNumber: responseSerialNumber ); } + + /// + /// 异步验证响应签名。 + /// + /// REF:
+ ///
+ /// + ///
+ ///
+ /// + /// + /// + /// + public static Task VerifyResponseSignatureAsync(this WechatTenpayClient client, TResponse response, CancellationToken cancellationToken = default) + where TResponse : WechatTenpayResponse + { + if (client is null) throw new ArgumentNullException(nameof(client)); + if (response is null) throw new ArgumentNullException(nameof(response)); + + return VerifyResponseSignatureAsync( + client, + responseTimestamp: response.WechatpayTimestamp, + responseNonce: response.WechatpayNonce, + responseBody: Encoding.UTF8.GetString(response.GetRawBytes()), + responseSignature: response.WechatpaySignature, + responseSignatureType: response.WechatpaySignatureType, + responseSerialNumber: response.WechatpayCertificateSerialNumber, + cancellationToken: cancellationToken + ); + } + + /// + /// 异步验证响应签名。 + /// + /// REF:
+ ///
+ /// + ///
+ ///
+ /// + /// + /// 。 + /// + /// + /// + /// + public static Task VerifyResponseSignatureAsync(this WechatTenpayClient client, string responseTimestamp, string responseNonce, string responseBody, string responseSignature, string responseSerialNumber, CancellationToken cancellationToken = default) + { + return VerifyResponseSignatureAsync( + client, + responseTimestamp: responseTimestamp, + responseNonce: responseNonce, + responseBody: responseBody, + responseSignature: responseSignature, + responseSignatureType: SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256, + responseSerialNumber: responseSerialNumber, + cancellationToken + ); + } + + /// + /// 异步验证响应签名。 + /// + /// REF:
+ ///
+ /// + ///
+ ///
+ /// + /// + /// 。 + /// + /// + /// + /// + /// + public static Task VerifyResponseSignatureAsync(this WechatTenpayClient client, string responseTimestamp, string responseNonce, string responseBody, string responseSignature, string responseSignatureType, string responseSerialNumber, CancellationToken cancellationToken = default) + { + if (client is null) throw new ArgumentNullException(nameof(client)); + + return WechatTenpayClientSigningExtensions.VerifySignatureAsync( + client, + strTimestamp: responseTimestamp, + strNonce: responseNonce, + strContent: responseBody, + strSignature: responseSignature, + strSignScheme: responseSignatureType, + strSerialNumber: responseSerialNumber, + cancellationToken + ); + } } } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/__Internal/WechatTenpayClientSigningExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/__Internal/WechatTenpayClientSigningExtensions.cs index 542542a8..5062bd4b 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/__Internal/WechatTenpayClientSigningExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/__Internal/WechatTenpayClientSigningExtensions.cs @@ -1,8 +1,11 @@ using System; +using System.Threading; +using System.Threading.Tasks; namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 { using SKIT.FlurlHttpClient.Primitives; + using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants; using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings; internal static class WechatTenpayClientSigningExtensions @@ -11,36 +14,68 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 { if (client is null) throw new ArgumentNullException(nameof(client)); + CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(strSerialNumber); + if (!entry.HasValue) + { + return ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate with serial number \"{strSerialNumber}\".")); + } + + return GenerateSignatureResultByCertificate( + scheme: strSignScheme, + certificate: entry.Value.Certificate, + message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent), + signature: strSignature + ); + } + + public static async Task 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.PlatformCertificateManager is not ICertificateManagerAsync) + { + // 降级为同步调用 + return VerifySignature(client, strTimestamp, strNonce, strContent, strSignature, strSignScheme, strSerialNumber); + } + + CertificateEntry? entry = await ((ICertificateManagerAsync)client.PlatformCertificateManager).GetEntryAsync(strSerialNumber, cancellationToken).ConfigureAwait(false); + if (!entry.HasValue) + { + return ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate with serial number \"{strSerialNumber}\".")); + } + + return GenerateSignatureResultByCertificate( + scheme: strSignScheme, + certificate: entry.Value.Certificate, + message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent), + signature: strSignature + ); + } + + private static string GenerateMessageForSignature(string timestamp, string nonce, string body) + { + return $"{timestamp}\n{nonce}\n{body}\n"; + } + + private static ErroredResult GenerateSignatureResultByCertificate(string scheme, string certificate, string message, string signature) + { ErroredResult result; - switch (strSignScheme) + switch (scheme) { - case Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256: + case SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256: { try { - if (client.PlatformCertificateManager is null) - { - result = ErroredResult.Fail(new Exception("The platform certificate manager is not initialized.")); - break; - } - - CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(strSerialNumber); - if (!entry.HasValue) - { - result = ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate with serial number \"{strSerialNumber}\".")); - break; - } - bool valid = Utilities.RSAUtility.VerifyByCertificate( - certificatePem: entry.Value.Certificate, - messageData: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent), - encodingSignature: new EncodedString(strSignature, EncodingKinds.Base64) + certificatePem: certificate, + messageData: message, + encodingSignature: new EncodedString(signature, EncodingKinds.Base64) ); if (valid) result = ErroredResult.Ok(); else - result = ErroredResult.Fail(new Exception($"Signature does not match. Maybe \"{strSignature}\" is an illegal signature.")); + result = ErroredResult.Fail(new Exception($"Signature does not match. Maybe \"{signature}\" is an illegal signature.")); } catch (Exception ex) { @@ -49,32 +84,19 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 } break; - case Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3: + case SignSchemes.WECHATPAY2_SM2_WITH_SM3: { try { - if (client.PlatformCertificateManager is null) - { - result = ErroredResult.Fail(new Exception("The platform certificate manager is not initialized.")); - break; - } - - CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(strSerialNumber); - if (!entry.HasValue) - { - result = ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate with serial number \"{strSerialNumber}\".")); - break; - } - bool valid = Utilities.SM2Utility.VerifyWithSM3ByCertificate( - certificatePem: entry.Value.Certificate, - messageData: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent), - encodingSignature: new EncodedString(strSignature, EncodingKinds.Base64) + certificatePem: certificate, + messageData: message, + encodingSignature: new EncodedString(signature, EncodingKinds.Base64) ); if (valid) result = ErroredResult.Ok(); else - result = ErroredResult.Fail(new Exception($"Signature does not match. Maybe \"{strSignature}\" is an illegal signature.")); + result = ErroredResult.Fail(new Exception($"Signature does not match. Maybe \"{signature}\" is an illegal signature.")); } catch (Exception ex) { @@ -85,17 +107,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 default: { - result = ErroredResult.Fail(new Exception($"Unsupported signing scheme: \"{strSignScheme}\".")); + result = ErroredResult.Fail(new Exception($"Unsupported signing scheme: \"{scheme}\".")); } break; } return result; } - - private static string GenerateMessageForSignature(string timestamp, string nonce, string body) - { - return $"{timestamp}\n{nonce}\n{body}\n"; - } } } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Interceptors/WechatTenpayRequestSigningInterceptor.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Interceptors/WechatTenpayRequestSigningInterceptor.cs index 59312caf..2927abc4 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Interceptors/WechatTenpayRequestSigningInterceptor.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Interceptors/WechatTenpayRequestSigningInterceptor.cs @@ -8,6 +8,7 @@ using Flurl.Http; namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Interceptors { using SKIT.FlurlHttpClient.Internal; + using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants; internal class WechatTenpayRequestSigningInterceptor : HttpInterceptor { @@ -38,7 +39,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Interceptors if (context.FlurlCall.HttpRequestMessage.Content is MultipartFormDataContent formdataContent) { // NOTICE: multipart/form-data 文件上传请求的待签名参数需特殊处理 - var httpContent = formdataContent.SingleOrDefault(e => Constants.FormDataFields.FORMDATA_META.Equals(e.Headers.ContentDisposition?.Name?.Trim('\"'))); + var httpContent = formdataContent.SingleOrDefault(e => FormDataFields.FORMDATA_META.Equals(e.Headers.ContentDisposition?.Name?.Trim('\"'))); if (httpContent is not null) { body = await _AsyncEx.RunTaskWithCancellationTokenAsync(httpContent.ReadAsStringAsync(), cancellationToken).ConfigureAwait(false); @@ -54,7 +55,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Interceptors switch (_scheme) { - case Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256: + case SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256: { try { @@ -67,7 +68,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Interceptors } break; - case Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3: + case SignSchemes.WECHATPAY2_SM2_WITH_SM3: { try { diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Settings/CertificateEntry.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Settings/CertificateEntry.cs index dfd38f17..8f98e8ea 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Settings/CertificateEntry.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Settings/CertificateEntry.cs @@ -7,7 +7,14 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings ///
public partial struct CertificateEntry : IEquatable { + /// + /// 证书算法类型:RSA。 + /// public const string ALGORITHM_TYPE_RSA = "RSA"; + + /// + /// 证书算法类型:SM2。 + /// public const string ALGORITHM_TYPE_SM2 = "SM2"; /// @@ -36,6 +43,14 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings /// public DateTimeOffset ExpireTime { get; } + /// + /// + /// + /// + /// + /// + /// + /// [Newtonsoft.Json.JsonConstructor] [System.Text.Json.Serialization.JsonConstructor] public CertificateEntry(string algorithmType, string serialNumber, string certificate, DateTimeOffset effectiveTime, DateTimeOffset expireTime) @@ -58,6 +73,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings ExpireTime = expireTime; } + /// + /// + /// + /// + /// public CertificateEntry(string algorithmType, string certificate) { if (string.IsNullOrEmpty(algorithmType)) @@ -95,12 +115,21 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings } } + /// + /// 返回一个布尔值,该值指示当前证书是否可用。 + /// + /// public bool IsAvailable() { DateTimeOffset now = DateTimeOffset.Now; return EffectiveTime <= now && now < ExpireTime; } + /// + /// + /// + /// + /// public bool Equals(CertificateEntry other) { return string.Equals(AlgorithmType, other.AlgorithmType) && @@ -108,6 +137,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings string.Equals(SerialNumber, other.SerialNumber); } + /// public override bool Equals(object? obj) { if (ReferenceEquals(null, obj)) @@ -118,20 +148,23 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings return Equals((CertificateEntry)obj); } + /// public override int GetHashCode() { -#if NETFRAMEWORK || NETSTANDARD2_0 - return (AlgorithmType?.GetHashCode(), Certificate?.GetHashCode(), SerialNumber?.GetHashCode()).GetHashCode(); -#else +#if NETCOREAPP || NET5_0_OR_GREATER return HashCode.Combine(AlgorithmType?.GetHashCode(), Certificate?.GetHashCode(), SerialNumber?.GetHashCode()); +#else + return (AlgorithmType?.GetHashCode(), Certificate?.GetHashCode(), SerialNumber?.GetHashCode()).GetHashCode(); #endif } + /// public static bool operator ==(CertificateEntry left, CertificateEntry right) { return left.Equals(right); } + /// public static bool operator !=(CertificateEntry left, CertificateEntry right) { return !left.Equals(right); @@ -140,11 +173,23 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings partial struct CertificateEntry { + /// + /// 将指定的 对象解析为 对象。 + /// + /// + /// + /// public static CertificateEntry Parse(string algorithmType, Models.QueryCertificatesResponse.Types.Certificate certificate) { return new CertificateEntry(algorithmType, certificate.SerialNumber, certificate.EncryptCertificate.CipherText, certificate.EffectiveTime, certificate.ExpireTime); } + /// + /// 将指定的 对象解析为 对象。 + /// + /// + /// + /// public static CertificateEntry Parse(Models.QueryCertificatesResponse.Types.Certificate certificate) { string? algorithmType = default!; diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Settings/CertificateManager.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Settings/CertificateManager.cs index 7f3a2a49..da81b120 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Settings/CertificateManager.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Settings/CertificateManager.cs @@ -2,45 +2,84 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings { /// /// 微信商户平台证书管理器接口。 /// - public abstract class CertificateManager + public interface ICertificateManager { /// /// 获取存储的全部证书实体。 /// /// - public abstract IEnumerable AllEntries(); + IEnumerable AllEntries(); /// /// 添加一个证书实体。 /// /// - public abstract void AddEntry(CertificateEntry entry); + void AddEntry(CertificateEntry entry); /// /// 根据证书序列号获取证书实体。 /// /// /// - public abstract CertificateEntry? GetEntry(string serialNumber); + CertificateEntry? GetEntry(string serialNumber); /// /// 根据证书序列号移除证书实体。 /// /// /// - public abstract bool RemoveEntry(string serialNumber); + bool RemoveEntry(string serialNumber); + } + + /// + /// 微信商户平台证书管理器异步接口。 + /// + public interface ICertificateManagerAsync : ICertificateManager + { + /// + /// 异步获取存储的全部证书实体。 + /// + /// + /// + Task> AllEntriesAsync(CancellationToken cancellationToken = default); + + /// + /// 异步添加一个证书实体。 + /// + /// + /// + /// + Task AddEntryAsync(CertificateEntry entry, CancellationToken cancellationToken = default); + + /// + /// 异步根据证书序列号获取证书实体。 + /// + /// + /// + /// + Task GetEntryAsync(string serialNumber, CancellationToken cancellationToken = default); + + /// + /// 异步根据证书序列号移除证书实体。 + /// + /// + /// + /// + Task RemoveEntryAsync(string serialNumber, CancellationToken cancellationToken = default); } /// /// 一个基于内存实现的 。 /// - public class InMemoryCertificateManager : CertificateManager + public sealed class InMemoryCertificateManager : ICertificateManager { private readonly ConcurrentDictionary _dict; @@ -49,20 +88,22 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings _dict = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); } - public override IEnumerable AllEntries() + /// + public IEnumerable AllEntries() { - return _dict.Values.Where(e => e.IsAvailable()).ToArray(); + return _dict.Values.Where(static e => e.IsAvailable()).ToArray(); } - public override void AddEntry(CertificateEntry entry) + /// + public void AddEntry(CertificateEntry entry) { - _dict.TryRemove(entry.SerialNumber, out _); - _dict.TryAdd(entry.SerialNumber, entry); + _dict.AddOrUpdate(entry.SerialNumber, (_) => entry, (_, _) => entry); } - public override CertificateEntry? GetEntry(string serialNumber) + /// + public CertificateEntry? GetEntry(string serialNumber) { - if (_dict.TryGetValue(serialNumber, out var entry)) + if (_dict.TryGetValue(serialNumber, out CertificateEntry entry)) { if (entry.IsAvailable()) return entry; @@ -73,7 +114,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings return null; } - public override bool RemoveEntry(string serialNumber) + /// + public bool RemoveEntry(string serialNumber) { return _dict.TryRemove(serialNumber, out _); } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Settings/Credentials.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Settings/Credentials.cs index 5e5c7051..ec046dfb 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Settings/Credentials.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Settings/Credentials.cs @@ -2,7 +2,7 @@ using System; namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings { - public class Credentials + public sealed class Credentials { /// /// 初始化客户端时 的副本。 diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/__Internal/FileHttpContentBuilder.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/__Internal/FileHttpContentBuilder.cs index d1e7d517..cbbff22e 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/__Internal/FileHttpContentBuilder.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Utilities/__Internal/FileHttpContentBuilder.cs @@ -6,6 +6,8 @@ using System.Web; namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities { + using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants; + internal static class FileHttpContentBuilder { public static MultipartFormDataContent Build(string fileName, byte[] fileBytes, string fileContentType, string fileMetaJson, string formDataName = "file") @@ -35,7 +37,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities string boundary = "--BOUNDARY--" + DateTimeOffset.Now.Ticks.ToString("x"); MultipartFormDataContent httpContent = new MultipartFormDataContent(boundary); httpContent.Headers.ContentType = MediaTypeHeaderValue.Parse($"multipart/form-data; boundary={boundary}"); - httpContent.Add(metaContent, $"\"{Constants.FormDataFields.FORMDATA_META}\""); + httpContent.Add(metaContent, $"\"{FormDataFields.FORMDATA_META}\""); httpContent.Add(fileContent, $"\"{formDataName}\"", $"\"{HttpUtility.UrlEncode(fileName)}\""); return httpContent; } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClient.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClient.cs index e02e32fc..b284c764 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClient.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClient.cs @@ -20,7 +20,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 /// /// 获取当前客户端使用的微信支付平台证书管理器。 /// - public Settings.CertificateManager PlatformCertificateManager { get; } + public Settings.ICertificateManager PlatformCertificateManager { get; } /// /// 获取是否自动加密请求中的敏感信息字段。 diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClientOptions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClientOptions.cs index e5433d18..b20a82ac 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClientOptions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClientOptions.cs @@ -82,6 +82,6 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 /// 默认值: /// /// - public Settings.CertificateManager PlatformCertificateManager { get; set; } = new Settings.InMemoryCertificateManager(); + public Settings.ICertificateManager PlatformCertificateManager { get; set; } = new Settings.InMemoryCertificateManager(); } } diff --git a/src/SKIT.FlurlHttpClient.Wechat.Work/ExtendedSDK/Finance/Settings/Credentials.cs b/src/SKIT.FlurlHttpClient.Wechat.Work/ExtendedSDK/Finance/Settings/Credentials.cs index 21396665..cc2ec9e1 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.Work/ExtendedSDK/Finance/Settings/Credentials.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.Work/ExtendedSDK/Finance/Settings/Credentials.cs @@ -2,7 +2,7 @@ using System; namespace SKIT.FlurlHttpClient.Wechat.Work.ExtendedSDK.Finance.Settings { - public class Credentials + public sealed class Credentials { /// /// 初始化客户端时 的副本。 diff --git a/src/SKIT.FlurlHttpClient.Wechat.Work/Settings/Credentials.cs b/src/SKIT.FlurlHttpClient.Wechat.Work/Settings/Credentials.cs index 419c2f59..413030e9 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.Work/Settings/Credentials.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.Work/Settings/Credentials.cs @@ -1,8 +1,8 @@ -using System; +using System; namespace SKIT.FlurlHttpClient.Wechat.Work.Settings { - public class Credentials + public sealed class Credentials { /// /// 初始化客户端时 的副本。