فهرست منبع

拆分 IWeChatPayClient、IWeChatPayNotifyClient

Roc 5 سال پیش
والد
کامیت
192aac6033

+ 1 - 0
samples/WebApplicationSample/Controllers/WeChatPayController.cs

@@ -1,6 +1,7 @@
 using System.Text.Json;
 using System.Text.Json;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Essensoft.AspNetCore.Payment.WeChatPay;
 using Essensoft.AspNetCore.Payment.WeChatPay;
+using Essensoft.AspNetCore.Payment.WeChatPay.V2;
 using Essensoft.AspNetCore.Payment.WeChatPay.V2.Request;
 using Essensoft.AspNetCore.Payment.WeChatPay.V2.Request;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Options;
 using Microsoft.Extensions.Options;

+ 1 - 0
samples/WebApplicationSample/Controllers/WeChatPayV3Controller.cs

@@ -1,6 +1,7 @@
 using System.Text.Json;
 using System.Text.Json;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Essensoft.AspNetCore.Payment.WeChatPay;
 using Essensoft.AspNetCore.Payment.WeChatPay;
+using Essensoft.AspNetCore.Payment.WeChatPay.V3;
 using Essensoft.AspNetCore.Payment.WeChatPay.V3.Domain;
 using Essensoft.AspNetCore.Payment.WeChatPay.V3.Domain;
 using Essensoft.AspNetCore.Payment.WeChatPay.V3.Request;
 using Essensoft.AspNetCore.Payment.WeChatPay.V3.Request;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.AspNetCore.Mvc;

+ 10 - 9
src/Essensoft.AspNetCore.Payment.WeChatPay/Extensions/ServiceCollectionExtensions.cs

@@ -1,5 +1,4 @@
-using Essensoft.AspNetCore.Payment.WeChatPay;
-using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.DependencyInjection.Extensions;
 using Microsoft.Extensions.Http;
 using Microsoft.Extensions.Http;
 
 
 namespace Microsoft.Extensions.DependencyInjection
 namespace Microsoft.Extensions.DependencyInjection
@@ -8,14 +7,16 @@ namespace Microsoft.Extensions.DependencyInjection
     {
     {
         public static void AddWeChatPay(this IServiceCollection services)
         public static void AddWeChatPay(this IServiceCollection services)
         {
         {
-            services.AddHttpClient(nameof(WeChatPayClient));
+            services.AddHttpClient(Essensoft.AspNetCore.Payment.WeChatPay.V2.WeChatPayClient.Name);
+            services.TryAddEnumerable(ServiceDescriptor.Singleton<IHttpMessageHandlerBuilderFilter, Essensoft.AspNetCore.Payment.WeChatPay.V2.WeChatPayHttpMessageHandlerBuilderFilter>());
+            services.AddSingleton<Essensoft.AspNetCore.Payment.WeChatPay.V2.WeChatPayClientCertificateManager>();
+            services.AddSingleton<Essensoft.AspNetCore.Payment.WeChatPay.V2.IWeChatPayClient, Essensoft.AspNetCore.Payment.WeChatPay.V2.WeChatPayClient>();
+            services.AddSingleton<Essensoft.AspNetCore.Payment.WeChatPay.V2.IWeChatPayNotifyClient, Essensoft.AspNetCore.Payment.WeChatPay.V2.WeChatPayNotifyClient>();
 
 
-            services.TryAddEnumerable(ServiceDescriptor.Singleton<IHttpMessageHandlerBuilderFilter, WeChatPayHttpMessageHandlerBuilderFilter>());
-
-            services.AddSingleton<WeChatPayClientCertificateManager>();
-            services.AddSingleton<WeChatPayPlatformCertificateManager>();
-            services.AddSingleton<IWeChatPayClient, WeChatPayClient>();
-            services.AddSingleton<IWeChatPayNotifyClient, WeChatPayNotifyClient>();
+            services.AddHttpClient(Essensoft.AspNetCore.Payment.WeChatPay.V3.WeChatPayClient.Name);
+            services.AddSingleton<Essensoft.AspNetCore.Payment.WeChatPay.V3.WeChatPayPlatformCertificateManager>();
+            services.AddSingleton<Essensoft.AspNetCore.Payment.WeChatPay.V3.IWeChatPayClient, Essensoft.AspNetCore.Payment.WeChatPay.V3.WeChatPayClient>();
+            services.AddSingleton<Essensoft.AspNetCore.Payment.WeChatPay.V3.IWeChatPayNotifyClient, Essensoft.AspNetCore.Payment.WeChatPay.V3.WeChatPayNotifyClient>();
         }
         }
     }
     }
 }
 }

+ 0 - 74
src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayClient.cs

@@ -1,74 +0,0 @@
-using System.Threading.Tasks;
-
-namespace Essensoft.AspNetCore.Payment.WeChatPay
-{
-    /// <summary>
-    /// WeChatPay 客户端
-    /// </summary>
-    public interface IWeChatPayClient
-    {
-        #region V2
-
-        /// <summary>
-        /// 执行 WeChatPay V2 请求。
-        /// </summary>
-        /// <param name="request">请求对象</param>
-        /// <param name="options">配置选项</param>
-        /// <returns>响应对象</returns>
-        Task<T> ExecuteAsync<T>(V2.IWeChatPayRequest<T> request, WeChatPayOptions options) where T : V2.WeChatPayResponse;
-
-        /// <summary>
-        /// 执行 WeChatPay V2 请求。
-        /// </summary>
-        /// <param name="request">请求对象</param>
-        /// <param name="options">配置选项</param>
-        /// <returns>响应对象</returns>
-        Task<T> PageExecuteAsync<T>(V2.IWeChatPayRequest<T> request, WeChatPayOptions options) where T : V2.WeChatPayResponse;
-
-        /// <summary>
-        /// 执行 WeChatPay V2 证书请求。
-        /// </summary>
-        /// <param name="request">请求对象</param>
-        /// <param name="options">配置选项</param>
-        /// <returns>响应对象</returns>
-        Task<T> ExecuteAsync<T>(V2.IWeChatPayCertRequest<T> request, WeChatPayOptions options) where T : V2.WeChatPayResponse;
-
-        /// <summary>
-        /// 执行 WeChatPay V2 Sdk请求。
-        /// </summary>
-        /// <param name="request">请求对象</param>
-        /// <param name="options">配置选项</param>
-        /// <returns>响应字典</returns>
-        Task<WeChatPayDictionary> ExecuteAsync(V2.IWeChatPaySdkRequest request, WeChatPayOptions options);
-
-        #endregion
-
-        #region V3
-
-        /// <summary>
-        /// 执行 WeChatPay V3 Sdk请求。
-        /// </summary>
-        /// <param name="request">请求对象</param>
-        /// <param name="options">配置选项</param>
-        /// <returns>响应字典</returns>
-        Task<WeChatPayDictionary> ExecuteAsync(V3.IWeChatPaySdkRequest request, WeChatPayOptions options);
-
-        /// <summary>
-        /// 执行 WeChatPay V3 Get请求。
-        /// </summary>
-        /// <param name="request">请求对象</param>
-        /// <param name="options">配置选项</param>
-        /// <returns>响应对象</returns>
-        Task<T> ExecuteAsync<T>(V3.IWeChatPayGetRequest<T> request, WeChatPayOptions options) where T : V3.WeChatPayResponse;
-
-        /// <summary>
-        /// 执行 WeChatPay V3 Post请求。
-        /// </summary>
-        /// <param name="request">请求对象</param>
-        /// <param name="options">配置选项</param>
-        /// <returns>响应对象</returns>
-        Task<T> ExecuteAsync<T>(V3.IWeChatPayPostRequest<T> request, WeChatPayOptions options) where T : V3.WeChatPayResponse;
-
-        #endregion
-    }
-}

+ 42 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/V2/IWeChatPayClient.cs

@@ -0,0 +1,42 @@
+using System.Threading.Tasks;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.V2
+{
+    /// <summary>
+    /// WeChatPay 客户端
+    /// </summary>
+    public interface IWeChatPayClient
+    {
+        /// <summary>
+        /// 执行 WeChatPay V2 请求。
+        /// </summary>
+        /// <param name="request">请求对象</param>
+        /// <param name="options">配置选项</param>
+        /// <returns>响应对象</returns>
+        Task<T> ExecuteAsync<T>(IWeChatPayRequest<T> request, WeChatPayOptions options) where T : WeChatPayResponse;
+
+        /// <summary>
+        /// 执行 WeChatPay V2 请求。
+        /// </summary>
+        /// <param name="request">请求对象</param>
+        /// <param name="options">配置选项</param>
+        /// <returns>响应对象</returns>
+        Task<T> PageExecuteAsync<T>(IWeChatPayRequest<T> request, WeChatPayOptions options) where T : WeChatPayResponse;
+
+        /// <summary>
+        /// 执行 WeChatPay V2 证书请求。
+        /// </summary>
+        /// <param name="request">请求对象</param>
+        /// <param name="options">配置选项</param>
+        /// <returns>响应对象</returns>
+        Task<T> ExecuteAsync<T>(IWeChatPayCertRequest<T> request, WeChatPayOptions options) where T : WeChatPayResponse;
+
+        /// <summary>
+        /// 执行 WeChatPay V2 Sdk请求。
+        /// </summary>
+        /// <param name="request">请求对象</param>
+        /// <param name="options">配置选项</param>
+        /// <returns>响应字典</returns>
+        Task<WeChatPayDictionary> ExecuteAsync(IWeChatPaySdkRequest request, WeChatPayOptions options);
+    }
+}

+ 29 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/V2/IWeChatPayNotifyClient.cs

@@ -0,0 +1,29 @@
+using System.Threading.Tasks;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.V2
+{
+    /// <summary>
+    /// WeChatPay 通知客户端
+    /// </summary>
+    public interface IWeChatPayNotifyClient
+    {
+#if NETCOREAPP3_1
+        /// <summary>
+        /// 执行 WeChatPay V2 通知请求解析
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">控制器的请求</param>
+        /// <param name="options">配置选项</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteV2Async<T>(Microsoft.AspNetCore.Http.HttpRequest request, WeChatPayOptions options) where T : V2.WeChatPayNotify;
+#endif
+        /// <summary>
+        /// 执行 WeChatPay V2 通知请求解析
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="body">通知内容</param>
+        /// <param name="options">配置选项</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteV2Async<T>(string body, WeChatPayOptions options) where T : V2.WeChatPayNotify;
+    }
+}

+ 5 - 193
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayClient.cs → src/Essensoft.AspNetCore.Payment.WeChatPay/V2/WeChatPayClient.cs

@@ -1,36 +1,29 @@
 using System;
 using System;
 using System.Net.Http;
 using System.Net.Http;
-using System.Security.Cryptography.X509Certificates;
-using System.Text;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
-using Essensoft.AspNetCore.Payment.Security;
 using Essensoft.AspNetCore.Payment.WeChatPay.Extensions;
 using Essensoft.AspNetCore.Payment.WeChatPay.Extensions;
 using Essensoft.AspNetCore.Payment.WeChatPay.V2.Parser;
 using Essensoft.AspNetCore.Payment.WeChatPay.V2.Parser;
-using Essensoft.AspNetCore.Payment.WeChatPay.V3.Parser;
 
 
-namespace Essensoft.AspNetCore.Payment.WeChatPay
+namespace Essensoft.AspNetCore.Payment.WeChatPay.V2
 {
 {
     public class WeChatPayClient : IWeChatPayClient
     public class WeChatPayClient : IWeChatPayClient
     {
     {
-        public const string Prefix = nameof(WeChatPayClient) + ".";
+        public const string Name = "WeChatPayClient-V2";
+        public const string Prefix = Name + ".";
 
 
         private readonly IHttpClientFactory _httpClientFactory;
         private readonly IHttpClientFactory _httpClientFactory;
         private readonly WeChatPayClientCertificateManager _clientCertificateManager;
         private readonly WeChatPayClientCertificateManager _clientCertificateManager;
-        private readonly WeChatPayPlatformCertificateManager _platformCertificateManager;
 
 
         #region WeChatPayClient Constructors
         #region WeChatPayClient Constructors
 
 
-        public WeChatPayClient(IHttpClientFactory httpClientFactory, WeChatPayClientCertificateManager clientCertificateManager, WeChatPayPlatformCertificateManager platformCertificateManager)
+        public WeChatPayClient(IHttpClientFactory httpClientFactory, WeChatPayClientCertificateManager clientCertificateManager)
         {
         {
             _httpClientFactory = httpClientFactory;
             _httpClientFactory = httpClientFactory;
             _clientCertificateManager = clientCertificateManager;
             _clientCertificateManager = clientCertificateManager;
-            _platformCertificateManager = platformCertificateManager;
         }
         }
 
 
         #endregion
         #endregion
 
 
-        #region V2
-
         #region IWeChatPayClient Members
         #region IWeChatPayClient Members
 
 
         public async Task<T> ExecuteAsync<T>(V2.IWeChatPayRequest<T> request, WeChatPayOptions options) where T : V2.WeChatPayResponse
         public async Task<T> ExecuteAsync<T>(V2.IWeChatPayRequest<T> request, WeChatPayOptions options) where T : V2.WeChatPayResponse
@@ -60,7 +53,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 
 
             request.PrimaryHandler(options, signType, sortedTxtParams);
             request.PrimaryHandler(options, signType, sortedTxtParams);
 
 
-            var client = _httpClientFactory.CreateClient(nameof(WeChatPayClient));
+            var client = _httpClientFactory.CreateClient(Name);
             var body = await client.PostAsync(request, sortedTxtParams);
             var body = await client.PostAsync(request, sortedTxtParams);
             var parser = new WeChatPayResponseXmlParser<T>();
             var parser = new WeChatPayResponseXmlParser<T>();
             var response = parser.Parse(body);
             var response = parser.Parse(body);
@@ -236,186 +229,5 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
         }
         }
 
 
         #endregion
         #endregion
-
-        #endregion
-
-        #region V3
-
-        #region IWeChatPayClient Members
-
-        public Task<WeChatPayDictionary> ExecuteAsync(V3.IWeChatPaySdkRequest request, WeChatPayOptions options)
-        {
-            if (options == null)
-            {
-                throw new ArgumentNullException(nameof(options));
-            }
-
-            if (string.IsNullOrEmpty(options.AppId))
-            {
-                throw new ArgumentNullException(nameof(options.AppId));
-            }
-
-            if (string.IsNullOrEmpty(options.MchId))
-            {
-                throw new ArgumentNullException(nameof(options.MchId));
-            }
-
-            if (string.IsNullOrEmpty(options.V3Key))
-            {
-                throw new ArgumentNullException(nameof(options.V3Key));
-            }
-
-            var sortedTxtParams = new WeChatPayDictionary(request.GetParameters());
-
-            request.PrimaryHandler(options, sortedTxtParams);
-
-            return Task.FromResult(sortedTxtParams);
-        }
-
-        #endregion
-
-        #region IWeChatPayClient Members
-
-        public async Task<T> ExecuteAsync<T>(V3.IWeChatPayGetRequest<T> request, WeChatPayOptions options) where T : V3.WeChatPayResponse
-        {
-            if (options == null)
-            {
-                throw new ArgumentNullException(nameof(options));
-            }
-
-            if (string.IsNullOrEmpty(options.MchId))
-            {
-                throw new ArgumentNullException(nameof(options.MchId));
-            }
-
-            if (string.IsNullOrEmpty(options.Certificate))
-            {
-                throw new ArgumentNullException(nameof(options.Certificate));
-            }
-
-            if (string.IsNullOrEmpty(options.V3Key))
-            {
-                throw new ArgumentNullException(nameof(options.V3Key));
-            }
-
-            var client = _httpClientFactory.CreateClient(nameof(WeChatPayClient));
-            var (serial, timestamp, nonce, signature, body, statusCode) = await client.GetAsync(request, options);
-            var parser = new WeChatPayResponseJsonParser<T>();
-            var response = parser.Parse(body, statusCode);
-
-            if (request.GetNeedCheckSign())
-            {
-                await CheckV3ResponseSignAsync(options, serial, timestamp, nonce, signature, body);
-            }
-
-            return response;
-        }
-
-        #endregion
-
-        #region IWeChatPayClient Members
-
-        public async Task<T> ExecuteAsync<T>(V3.IWeChatPayPostRequest<T> request, WeChatPayOptions options) where T : V3.WeChatPayResponse
-        {
-            if (options == null)
-            {
-                throw new ArgumentNullException(nameof(options));
-            }
-
-            if (string.IsNullOrEmpty(options.MchId))
-            {
-                throw new ArgumentNullException(nameof(options.MchId));
-            }
-
-            if (string.IsNullOrEmpty(options.Certificate))
-            {
-                throw new ArgumentNullException(nameof(options.Certificate));
-            }
-
-            var client = _httpClientFactory.CreateClient(nameof(WeChatPayClient));
-            var (serial, timestamp, nonce, signature, body, statusCode) = await client.PostAsync(request, options);
-            var parser = new WeChatPayResponseJsonParser<T>();
-            var response = parser.Parse(body, statusCode);
-
-            await CheckV3ResponseSignAsync(options, serial, timestamp, nonce, signature, body);
-
-            return response;
-        }
-
-        #endregion
-
-        #region Check Response Method
-
-        private async Task CheckV3ResponseSignAsync(WeChatPayOptions options, string serial, string timestamp, string nonce, string signature, string body)
-        {
-            if (string.IsNullOrEmpty(serial))
-            {
-                throw new WeChatPayException($"sign check fail: {nameof(serial)} is empty!");
-            }
-
-            if (string.IsNullOrEmpty(signature))
-            {
-                throw new WeChatPayException($"sign check fail: {nameof(signature)} is empty!");
-            }
-
-            var cert = await LoadPlatformCertificateAsync(serial, options);
-            var signatureSourceData = BuildSignatureSourceData(timestamp, nonce, body);
-
-            if (!SHA256WithRSA.Verify(cert.GetRSAPublicKey(), signatureSourceData, signature))
-            {
-                throw new WeChatPayException("sign check fail: check Sign and Data Fail!");
-            }
-        }
-
-        private string BuildSignatureSourceData(string timestamp, string nonce, string body)
-        {
-            return $"{timestamp}\n{nonce}\n{body}\n";
-        }
-
-        private async Task<X509Certificate2> LoadPlatformCertificateAsync(string serial, WeChatPayOptions options)
-        {
-            // 如果证书序列号已缓存,则直接使用缓存的
-            if (_platformCertificateManager.TryGetValue(serial, out var certificate2))
-            {
-                return certificate2;
-            }
-
-            // 否则重新下载新的平台证书
-            var request = new V3.Request.WeChatPayCertificatesRequest();
-            var response = await ExecuteAsync(request, options);
-            foreach (var certificate in response.Certificates)
-            {
-                // 若证书序列号未被缓存,解密证书并加入缓存
-                if (!_platformCertificateManager.ContainsKey(certificate.SerialNo))
-                {
-                    switch (certificate.EncryptCertificate.Algorithm)
-                    {
-                        case nameof(AEAD_AES_256_GCM):
-                            {
-                                var certStr = AEAD_AES_256_GCM.Decrypt(certificate.EncryptCertificate.Nonce, certificate.EncryptCertificate.Ciphertext, certificate.EncryptCertificate.AssociatedData, options.V3Key);
-                                var cert = new X509Certificate2(Encoding.UTF8.GetBytes(certStr));
-                                _platformCertificateManager.TryAdd(certificate.SerialNo, cert);
-                            }
-                            break;
-                        default:
-                            throw new WeChatPayException($"Unknown algorithm: {certificate.EncryptCertificate.Algorithm}");
-                    }
-                }
-            }
-
-            // 重新从缓存获取
-            if (_platformCertificateManager.TryGetValue(serial, out certificate2))
-            {
-                return certificate2;
-            }
-            else
-            {
-                throw new WeChatPayException("Download certificates failed!");
-            }
-        }
-
-        #endregion
-
-        #endregion
     }
     }
 }
 }

+ 1 - 1
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayClientCertificateManager.cs → src/Essensoft.AspNetCore.Payment.WeChatPay/V2/WeChatPayClientCertificateManager.cs

@@ -1,7 +1,7 @@
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
 using System.Security.Cryptography.X509Certificates;
 using System.Security.Cryptography.X509Certificates;
 
 
-namespace Essensoft.AspNetCore.Payment.WeChatPay
+namespace Essensoft.AspNetCore.Payment.WeChatPay.V2
 {
 {
     public class WeChatPayClientCertificateManager
     public class WeChatPayClientCertificateManager
     {
     {

+ 1 - 1
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayHttpMessageHandlerBuilderFilter.cs → src/Essensoft.AspNetCore.Payment.WeChatPay/V2/WeChatPayHttpMessageHandlerBuilderFilter.cs

@@ -2,7 +2,7 @@
 using System.Net.Http;
 using System.Net.Http;
 using Microsoft.Extensions.Http;
 using Microsoft.Extensions.Http;
 
 
-namespace Essensoft.AspNetCore.Payment.WeChatPay
+namespace Essensoft.AspNetCore.Payment.WeChatPay.V2
 {
 {
     public class WeChatPayHttpMessageHandlerBuilderFilter : IHttpMessageHandlerBuilderFilter
     public class WeChatPayHttpMessageHandlerBuilderFilter : IHttpMessageHandlerBuilderFilter
     {
     {

+ 104 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/V2/WeChatPayNotifyClient.cs

@@ -0,0 +1,104 @@
+using System;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.Security;
+using Essensoft.AspNetCore.Payment.WeChatPay.V2.Parser;
+using MD5 = Essensoft.AspNetCore.Payment.Security.MD5;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.V2
+{
+    public class WeChatPayNotifyClient : IWeChatPayNotifyClient
+    {
+        #region WeChatPayNotifyClient Constructors
+
+        public WeChatPayNotifyClient()
+        {
+        }
+
+        #endregion
+
+        #region IWeChatPayNotifyClient Members
+
+#if NETCOREAPP3_1
+        public async Task<T> ExecuteV2Async<T>(Microsoft.AspNetCore.Http.HttpRequest request, WeChatPayOptions options) where T : V2.WeChatPayNotify
+        {
+            if (request == null)
+            {
+                throw new ArgumentNullException(nameof(request));
+            }
+
+            var body = await new StreamReader(request.Body, Encoding.UTF8).ReadToEndAsync();
+            return await ExecuteV2Async<T>(body, options);
+        }
+#endif
+
+        #endregion
+
+        #region IWeChatPayNotifyClient Members
+
+        public Task<T> ExecuteV2Async<T>(string body, WeChatPayOptions options) where T : V2.WeChatPayNotify
+        {
+            if (string.IsNullOrEmpty(body))
+            {
+                throw new ArgumentNullException(nameof(body));
+            }
+
+            if (options == null)
+            {
+                throw new ArgumentNullException(nameof(options));
+            }
+
+            if (string.IsNullOrEmpty(options.Key))
+            {
+                throw new ArgumentNullException(nameof(options.Key));
+            }
+
+            var parser = new WeChatPayNotifyXmlParser<T>();
+            var notify = parser.Parse(body);
+            if (notify is V2.Notify.WeChatPayRefundNotify)
+            {
+                var key = MD5.Compute(options.Key).ToLowerInvariant();
+                var data = AES.Decrypt((notify as V2.Notify.WeChatPayRefundNotify).ReqInfo, key, CipherMode.ECB, PaddingMode.PKCS7);
+                notify = parser.Parse(body, data);
+            }
+            else
+            {
+                CheckNotifySign(notify, options);
+            }
+
+            return Task.FromResult(notify);
+        }
+
+        #endregion
+
+        #region Common Method
+
+        private void CheckNotifySign(V2.WeChatPayNotify notify, WeChatPayOptions options)
+        {
+            if (string.IsNullOrEmpty(notify.Body))
+            {
+                throw new WeChatPayException("sign check fail: Body is Empty!");
+            }
+
+            if (notify.Parameters.Count == 0)
+            {
+                throw new WeChatPayException("sign check fail: Parameters is Empty!");
+            }
+
+            if (!notify.Parameters.TryGetValue("sign", out var sign))
+            {
+                throw new WeChatPayException("sign check fail: sign is Empty!");
+            }
+
+            var cal_sign = WeChatPaySignature.SignWithKey(notify.Parameters, options.Key, WeChatPaySignType.MD5);
+            if (cal_sign != sign)
+            {
+                throw new WeChatPayException("sign check fail: check Sign and Data Fail!");
+            }
+        }
+
+        #endregion
+    }
+}

+ 34 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/V3/IWeChatPayClient.cs

@@ -0,0 +1,34 @@
+using System.Threading.Tasks;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.V3
+{
+    /// <summary>
+    /// WeChatPay 客户端
+    /// </summary>
+    public interface IWeChatPayClient
+    {
+        /// <summary>
+        /// 执行 WeChatPay V3 Sdk请求。
+        /// </summary>
+        /// <param name="request">请求对象</param>
+        /// <param name="options">配置选项</param>
+        /// <returns>响应字典</returns>
+        Task<WeChatPayDictionary> ExecuteAsync(IWeChatPaySdkRequest request, WeChatPayOptions options);
+
+        /// <summary>
+        /// 执行 WeChatPay V3 Get请求。
+        /// </summary>
+        /// <param name="request">请求对象</param>
+        /// <param name="options">配置选项</param>
+        /// <returns>响应对象</returns>
+        Task<T> ExecuteAsync<T>(IWeChatPayGetRequest<T> request, WeChatPayOptions options) where T : WeChatPayResponse;
+
+        /// <summary>
+        /// 执行 WeChatPay V3 Post请求。
+        /// </summary>
+        /// <param name="request">请求对象</param>
+        /// <param name="options">配置选项</param>
+        /// <returns>响应对象</returns>
+        Task<T> ExecuteAsync<T>(IWeChatPayPostRequest<T> request, WeChatPayOptions options) where T : WeChatPayResponse;
+    }
+}

+ 3 - 30
src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayNotifyClient.cs → src/Essensoft.AspNetCore.Payment.WeChatPay/V3/IWeChatPayNotifyClient.cs

@@ -1,37 +1,12 @@
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
-namespace Essensoft.AspNetCore.Payment.WeChatPay
+namespace Essensoft.AspNetCore.Payment.WeChatPay.V3
 {
 {
     /// <summary>
     /// <summary>
     /// WeChatPay 通知客户端
     /// WeChatPay 通知客户端
     /// </summary>
     /// </summary>
     public interface IWeChatPayNotifyClient
     public interface IWeChatPayNotifyClient
     {
     {
-        #region V2
-
-#if NETCOREAPP3_1
-        /// <summary>
-        /// 执行 WeChatPay V2 通知请求解析
-        /// </summary>
-        /// <typeparam name="T">领域对象</typeparam>
-        /// <param name="request">控制器的请求</param>
-        /// <param name="options">配置选项</param>
-        /// <returns>领域对象</returns>
-        Task<T> ExecuteV2Async<T>(Microsoft.AspNetCore.Http.HttpRequest request, WeChatPayOptions options) where T : V2.WeChatPayNotify;
-#endif
-        /// <summary>
-        /// 执行 WeChatPay V2 通知请求解析
-        /// </summary>
-        /// <typeparam name="T">领域对象</typeparam>
-        /// <param name="body">通知内容</param>
-        /// <param name="options">配置选项</param>
-        /// <returns>领域对象</returns>
-        Task<T> ExecuteV2Async<T>(string body, WeChatPayOptions options) where T : V2.WeChatPayNotify;
-
-        #endregion
-
-        #region V3
-
 #if NETCOREAPP3_1
 #if NETCOREAPP3_1
         /// <summary>
         /// <summary>
         /// 执行 WeChatPay V3 通知请求解析
         /// 执行 WeChatPay V3 通知请求解析
@@ -40,7 +15,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
         /// <param name="request">控制器的请求</param>
         /// <param name="request">控制器的请求</param>
         /// <param name="options">配置选项</param>
         /// <param name="options">配置选项</param>
         /// <returns>领域对象</returns>
         /// <returns>领域对象</returns>
-        Task<T> ExecuteV3Async<T>(Microsoft.AspNetCore.Http.HttpRequest request, WeChatPayOptions options) where T : V3.WeChatPayNotify;
+        Task<T> ExecuteV3Async<T>(Microsoft.AspNetCore.Http.HttpRequest request, WeChatPayOptions options) where T : WeChatPayNotify;
 #endif
 #endif
         /// <summary>
         /// <summary>
         /// 执行 WeChatPay V3 通知请求解析
         /// 执行 WeChatPay V3 通知请求解析
@@ -53,8 +28,6 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
         /// <param name="signature">Wechatpay_Signature</param>
         /// <param name="signature">Wechatpay_Signature</param>
         /// <param name="options">配置选项</param>
         /// <param name="options">配置选项</param>
         /// <returns>领域对象</returns>
         /// <returns>领域对象</returns>
-        Task<T> ExecuteV3Async<T>(string body, string serial, string timestamp, string nonce, string signature, WeChatPayOptions options) where T : V3.WeChatPayNotify;
-
-        #endregion
+        Task<T> ExecuteV3Async<T>(string body, string serial, string timestamp, string nonce, string signature, WeChatPayOptions options) where T : WeChatPayNotify;
     }
     }
 }
 }

+ 204 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/V3/WeChatPayClient.cs

@@ -0,0 +1,204 @@
+using System;
+using System.Net.Http;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.Security;
+using Essensoft.AspNetCore.Payment.WeChatPay.Extensions;
+using Essensoft.AspNetCore.Payment.WeChatPay.V3.Parser;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.V3
+{
+    public class WeChatPayClient : IWeChatPayClient
+    {
+        public const string Name = "WeChatPayClient-V3";
+
+        private readonly IHttpClientFactory _httpClientFactory;
+        private readonly WeChatPayPlatformCertificateManager _platformCertificateManager;
+
+        #region WeChatPayClient Constructors
+
+        public WeChatPayClient(IHttpClientFactory httpClientFactory, WeChatPayPlatformCertificateManager platformCertificateManager)
+        {
+            _httpClientFactory = httpClientFactory;
+            _platformCertificateManager = platformCertificateManager;
+        }
+
+        #endregion
+
+        #region IWeChatPayClient Members
+
+        public Task<WeChatPayDictionary> ExecuteAsync(IWeChatPaySdkRequest request, WeChatPayOptions options)
+        {
+            if (options == null)
+            {
+                throw new ArgumentNullException(nameof(options));
+            }
+
+            if (string.IsNullOrEmpty(options.AppId))
+            {
+                throw new ArgumentNullException(nameof(options.AppId));
+            }
+
+            if (string.IsNullOrEmpty(options.MchId))
+            {
+                throw new ArgumentNullException(nameof(options.MchId));
+            }
+
+            if (string.IsNullOrEmpty(options.V3Key))
+            {
+                throw new ArgumentNullException(nameof(options.V3Key));
+            }
+
+            var sortedTxtParams = new WeChatPayDictionary(request.GetParameters());
+
+            request.PrimaryHandler(options, sortedTxtParams);
+
+            return Task.FromResult(sortedTxtParams);
+        }
+
+        #endregion
+
+        #region IWeChatPayClient Members
+
+        public async Task<T> ExecuteAsync<T>(IWeChatPayGetRequest<T> request, WeChatPayOptions options) where T : WeChatPayResponse
+        {
+            if (options == null)
+            {
+                throw new ArgumentNullException(nameof(options));
+            }
+
+            if (string.IsNullOrEmpty(options.MchId))
+            {
+                throw new ArgumentNullException(nameof(options.MchId));
+            }
+
+            if (string.IsNullOrEmpty(options.Certificate))
+            {
+                throw new ArgumentNullException(nameof(options.Certificate));
+            }
+
+            if (string.IsNullOrEmpty(options.V3Key))
+            {
+                throw new ArgumentNullException(nameof(options.V3Key));
+            }
+
+            var client = _httpClientFactory.CreateClient(Name);
+            var (serial, timestamp, nonce, signature, body, statusCode) = await client.GetAsync(request, options);
+            var parser = new WeChatPayResponseJsonParser<T>();
+            var response = parser.Parse(body, statusCode);
+
+            if (request.GetNeedCheckSign())
+            {
+                await CheckV3ResponseSignAsync(options, serial, timestamp, nonce, signature, body);
+            }
+
+            return response;
+        }
+
+        #endregion
+
+        #region IWeChatPayClient Members
+
+        public async Task<T> ExecuteAsync<T>(IWeChatPayPostRequest<T> request, WeChatPayOptions options) where T : WeChatPayResponse
+        {
+            if (options == null)
+            {
+                throw new ArgumentNullException(nameof(options));
+            }
+
+            if (string.IsNullOrEmpty(options.MchId))
+            {
+                throw new ArgumentNullException(nameof(options.MchId));
+            }
+
+            if (string.IsNullOrEmpty(options.Certificate))
+            {
+                throw new ArgumentNullException(nameof(options.Certificate));
+            }
+
+            var client = _httpClientFactory.CreateClient(Name);
+            var (serial, timestamp, nonce, signature, body, statusCode) = await client.PostAsync(request, options);
+            var parser = new WeChatPayResponseJsonParser<T>();
+            var response = parser.Parse(body, statusCode);
+
+            await CheckV3ResponseSignAsync(options, serial, timestamp, nonce, signature, body);
+
+            return response;
+        }
+
+        #endregion
+
+        #region Check Response Method
+
+        private async Task CheckV3ResponseSignAsync(WeChatPayOptions options, string serial, string timestamp, string nonce, string signature, string body)
+        {
+            if (string.IsNullOrEmpty(serial))
+            {
+                throw new WeChatPayException($"sign check fail: {nameof(serial)} is empty!");
+            }
+
+            if (string.IsNullOrEmpty(signature))
+            {
+                throw new WeChatPayException($"sign check fail: {nameof(signature)} is empty!");
+            }
+
+            var cert = await LoadPlatformCertificateAsync(serial, options);
+            var signatureSourceData = BuildSignatureSourceData(timestamp, nonce, body);
+
+            if (!SHA256WithRSA.Verify(cert.GetRSAPublicKey(), signatureSourceData, signature))
+            {
+                throw new WeChatPayException("sign check fail: check Sign and Data Fail!");
+            }
+        }
+
+        private string BuildSignatureSourceData(string timestamp, string nonce, string body)
+        {
+            return $"{timestamp}\n{nonce}\n{body}\n";
+        }
+
+        private async Task<X509Certificate2> LoadPlatformCertificateAsync(string serial, WeChatPayOptions options)
+        {
+            // 如果证书序列号已缓存,则直接使用缓存的
+            if (_platformCertificateManager.TryGetValue(serial, out var certificate2))
+            {
+                return certificate2;
+            }
+
+            // 否则重新下载新的平台证书
+            var request = new Request.WeChatPayCertificatesRequest();
+            var response = await ExecuteAsync(request, options);
+            foreach (var certificate in response.Certificates)
+            {
+                // 若证书序列号未被缓存,解密证书并加入缓存
+                if (!_platformCertificateManager.ContainsKey(certificate.SerialNo))
+                {
+                    switch (certificate.EncryptCertificate.Algorithm)
+                    {
+                        case nameof(AEAD_AES_256_GCM):
+                            {
+                                var certStr = AEAD_AES_256_GCM.Decrypt(certificate.EncryptCertificate.Nonce, certificate.EncryptCertificate.Ciphertext, certificate.EncryptCertificate.AssociatedData, options.V3Key);
+                                var cert = new X509Certificate2(Encoding.UTF8.GetBytes(certStr));
+                                _platformCertificateManager.TryAdd(certificate.SerialNo, cert);
+                            }
+                            break;
+                        default:
+                            throw new WeChatPayException($"Unknown algorithm: {certificate.EncryptCertificate.Algorithm}");
+                    }
+                }
+            }
+
+            // 重新从缓存获取
+            if (_platformCertificateManager.TryGetValue(serial, out certificate2))
+            {
+                return certificate2;
+            }
+            else
+            {
+                throw new WeChatPayException("Download certificates failed!");
+            }
+        }
+
+        #endregion
+    }
+}

+ 4 - 97
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayNotifyClient.cs → src/Essensoft.AspNetCore.Payment.WeChatPay/V3/WeChatPayNotifyClient.cs

@@ -1,16 +1,13 @@
 using System;
 using System;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
-using System.Security.Cryptography;
 using System.Security.Cryptography.X509Certificates;
 using System.Security.Cryptography.X509Certificates;
 using System.Text;
 using System.Text;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Essensoft.AspNetCore.Payment.Security;
 using Essensoft.AspNetCore.Payment.Security;
-using Essensoft.AspNetCore.Payment.WeChatPay.V2.Parser;
 using Essensoft.AspNetCore.Payment.WeChatPay.V3.Parser;
 using Essensoft.AspNetCore.Payment.WeChatPay.V3.Parser;
-using MD5 = Essensoft.AspNetCore.Payment.Security.MD5;
 
 
-namespace Essensoft.AspNetCore.Payment.WeChatPay
+namespace Essensoft.AspNetCore.Payment.WeChatPay.V3
 {
 {
     public class WeChatPayNotifyClient : IWeChatPayNotifyClient
     public class WeChatPayNotifyClient : IWeChatPayNotifyClient
     {
     {
@@ -27,98 +24,10 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 
 
         #endregion
         #endregion
 
 
-        #region V2
-
-        #region IWeChatPayNotifyClient Members
-
-#if NETCOREAPP3_1
-        public async Task<T> ExecuteV2Async<T>(Microsoft.AspNetCore.Http.HttpRequest request, WeChatPayOptions options) where T : V2.WeChatPayNotify
-        {
-            if (request == null)
-            {
-                throw new ArgumentNullException(nameof(request));
-            }
-
-            var body = await new StreamReader(request.Body, Encoding.UTF8).ReadToEndAsync();
-            return await ExecuteV2Async<T>(body, options);
-        }
-#endif
-
-        #endregion
-
-        #region IWeChatPayNotifyClient Members
-
-        public Task<T> ExecuteV2Async<T>(string body, WeChatPayOptions options) where T : V2.WeChatPayNotify
-        {
-            if (string.IsNullOrEmpty(body))
-            {
-                throw new ArgumentNullException(nameof(body));
-            }
-
-            if (options == null)
-            {
-                throw new ArgumentNullException(nameof(options));
-            }
-
-            if (string.IsNullOrEmpty(options.Key))
-            {
-                throw new ArgumentNullException(nameof(options.Key));
-            }
-
-            var parser = new WeChatPayNotifyXmlParser<T>();
-            var notify = parser.Parse(body);
-            if (notify is V2.Notify.WeChatPayRefundNotify)
-            {
-                var key = MD5.Compute(options.Key).ToLowerInvariant();
-                var data = AES.Decrypt((notify as V2.Notify.WeChatPayRefundNotify).ReqInfo, key, CipherMode.ECB, PaddingMode.PKCS7);
-                notify = parser.Parse(body, data);
-            }
-            else
-            {
-                CheckNotifySign(notify, options);
-            }
-
-            return Task.FromResult(notify);
-        }
-
-        #endregion
-
-        #region Common Method
-
-        private void CheckNotifySign(V2.WeChatPayNotify notify, WeChatPayOptions options)
-        {
-            if (string.IsNullOrEmpty(notify.Body))
-            {
-                throw new WeChatPayException("sign check fail: Body is Empty!");
-            }
-
-            if (notify.Parameters.Count == 0)
-            {
-                throw new WeChatPayException("sign check fail: Parameters is Empty!");
-            }
-
-            if (!notify.Parameters.TryGetValue("sign", out var sign))
-            {
-                throw new WeChatPayException("sign check fail: sign is Empty!");
-            }
-
-            var cal_sign = WeChatPaySignature.SignWithKey(notify.Parameters, options.Key, WeChatPaySignType.MD5);
-            if (cal_sign != sign)
-            {
-                throw new WeChatPayException("sign check fail: check Sign and Data Fail!");
-            }
-        }
-
-        #endregion
-
-        #endregion
-
-        #region V3
-
         #region IWeChatPayNotifyClient Members
         #region IWeChatPayNotifyClient Members
 
 
 #if NETCOREAPP3_1
 #if NETCOREAPP3_1
-        public async Task<T> ExecuteV3Async<T>(Microsoft.AspNetCore.Http.HttpRequest request, WeChatPayOptions options) where T : V3.WeChatPayNotify
+        public async Task<T> ExecuteV3Async<T>(Microsoft.AspNetCore.Http.HttpRequest request, WeChatPayOptions options) where T : WeChatPayNotify
         {
         {
             if (options == null)
             if (options == null)
             {
             {
@@ -160,7 +69,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 
 
         #region IWeChatPayNotifyClient Members
         #region IWeChatPayNotifyClient Members
 
 
-        public async Task<T> ExecuteV3Async<T>(string body, string serial, string timestamp, string nonce, string signature, WeChatPayOptions options) where T : V3.WeChatPayNotify
+        public async Task<T> ExecuteV3Async<T>(string body, string serial, string timestamp, string nonce, string signature, WeChatPayOptions options) where T : WeChatPayNotify
         {
         {
             if (options == null)
             if (options == null)
             {
             {
@@ -224,7 +133,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
             }
             }
 
 
             // 否则重新下载新的平台证书
             // 否则重新下载新的平台证书
-            var request = new V3.Request.WeChatPayCertificatesRequest();
+            var request = new Request.WeChatPayCertificatesRequest();
             var response = await _client.ExecuteAsync(request, options);
             var response = await _client.ExecuteAsync(request, options);
             if (response.Certificates.Count > 0 && _platformCertificateManager.TryGetValue(serial, out certificate2))
             if (response.Certificates.Count > 0 && _platformCertificateManager.TryGetValue(serial, out certificate2))
             {
             {
@@ -237,7 +146,5 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
         }
         }
 
 
         #endregion
         #endregion
-
-        #endregion
     }
     }
 }
 }

+ 1 - 1
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayPlatformCertificateManager.cs → src/Essensoft.AspNetCore.Payment.WeChatPay/V3/WeChatPayPlatformCertificateManager.cs

@@ -1,7 +1,7 @@
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
 using System.Security.Cryptography.X509Certificates;
 using System.Security.Cryptography.X509Certificates;
 
 
-namespace Essensoft.AspNetCore.Payment.WeChatPay
+namespace Essensoft.AspNetCore.Payment.WeChatPay.V3
 {
 {
     public class WeChatPayPlatformCertificateManager
     public class WeChatPayPlatformCertificateManager
     {
     {