Browse Source

升级依赖项版本
引入 IHttpClientFactory

Roc 7 years ago
parent
commit
65a0e8dc32
82 changed files with 884 additions and 912 deletions
  1. 26 5
      README.MD
  2. 1 1
      samples/NewWebApplicationSample/NewWebApplicationSample.csproj
  3. 18 3
      samples/NewWebApplicationSample/Startup.cs
  4. 35 39
      src/Essensoft.AspNetCore.Payment.Alipay/AlipayClient.cs
  5. 12 16
      src/Essensoft.AspNetCore.Payment.Alipay/AlipayNotifyClient.cs
  6. 2 2
      src/Essensoft.AspNetCore.Payment.Alipay/AlipayResponse.cs
  7. 2 1
      src/Essensoft.AspNetCore.Payment.Alipay/Essensoft.AspNetCore.Payment.Alipay.csproj
  8. 2 4
      src/Essensoft.AspNetCore.Payment.Alipay/IAlipayClient.cs
  9. 2 2
      src/Essensoft.AspNetCore.Payment.Alipay/IAlipayNotifyClient.cs
  10. 2 2
      src/Essensoft.AspNetCore.Payment.Alipay/Parser/AlipayDictionaryParser.cs
  11. 3 3
      src/Essensoft.AspNetCore.Payment.Alipay/Parser/AlipayJsonParser.cs
  12. 4 4
      src/Essensoft.AspNetCore.Payment.Alipay/Parser/AlipayXmlParser.cs
  13. 8 1
      src/Essensoft.AspNetCore.Payment.Alipay/ServiceCollectionExtensions.cs
  14. 27 0
      src/Essensoft.AspNetCore.Payment.Alipay/Utility/AlipayUtility.cs
  15. 13 43
      src/Essensoft.AspNetCore.Payment.Alipay/Utility/HttpClientUtility.cs
  16. 2 1
      src/Essensoft.AspNetCore.Payment.JDPay/Essensoft.AspNetCore.Payment.JDPay.csproj
  17. 0 2
      src/Essensoft.AspNetCore.Payment.JDPay/IJDPayClient.cs
  18. 2 2
      src/Essensoft.AspNetCore.Payment.JDPay/IJDPayNotifyClient.cs
  19. 77 83
      src/Essensoft.AspNetCore.Payment.JDPay/JDPayClient.cs
  20. 16 23
      src/Essensoft.AspNetCore.Payment.JDPay/JDPayNotifyClient.cs
  21. 2 2
      src/Essensoft.AspNetCore.Payment.JDPay/Parser/JDPayDictionaryParser.cs
  22. 8 1
      src/Essensoft.AspNetCore.Payment.JDPay/ServiceCollectionExtensions.cs
  23. 0 52
      src/Essensoft.AspNetCore.Payment.JDPay/Utility/HttpClientEx.cs
  24. 25 0
      src/Essensoft.AspNetCore.Payment.JDPay/Utility/HttpClientUtility.cs
  25. 1 1
      src/Essensoft.AspNetCore.Payment.JDPay/Utility/JDPayContants.cs
  26. 18 18
      src/Essensoft.AspNetCore.Payment.JDPay/Utility/JDPaySecurity.cs
  27. 24 2
      src/Essensoft.AspNetCore.Payment.JDPay/Utility/JDPayUtility.cs
  28. 2 1
      src/Essensoft.AspNetCore.Payment.LianLianPay/Essensoft.AspNetCore.Payment.LianLianPay.csproj
  29. 0 2
      src/Essensoft.AspNetCore.Payment.LianLianPay/ILianLianPayClient.cs
  30. 2 2
      src/Essensoft.AspNetCore.Payment.LianLianPay/ILianLianPayNotifyClient.cs
  31. 36 45
      src/Essensoft.AspNetCore.Payment.LianLianPay/LianLianPayClient.cs
  32. 10 14
      src/Essensoft.AspNetCore.Payment.LianLianPay/LianLianPayNotifyClient.cs
  33. 2 2
      src/Essensoft.AspNetCore.Payment.LianLianPay/Parser/LianLianPayDictionaryParser.cs
  34. 2 2
      src/Essensoft.AspNetCore.Payment.LianLianPay/Parser/LianLianPayJsonParser.cs
  35. 8 1
      src/Essensoft.AspNetCore.Payment.LianLianPay/ServiceCollectionExtensions.cs
  36. 0 55
      src/Essensoft.AspNetCore.Payment.LianLianPay/Utility/HttpClientEx.cs
  37. 28 0
      src/Essensoft.AspNetCore.Payment.LianLianPay/Utility/HttpClientUtility.cs
  38. 2 2
      src/Essensoft.AspNetCore.Payment.LianLianPay/Utility/LianLianPaySecurity.cs
  39. 25 2
      src/Essensoft.AspNetCore.Payment.LianLianPay/Utility/LianLianPayUtility.cs
  40. 2 1
      src/Essensoft.AspNetCore.Payment.QPay/Essensoft.AspNetCore.Payment.QPay.csproj
  41. 0 2
      src/Essensoft.AspNetCore.Payment.QPay/IQPayClient.cs
  42. 2 2
      src/Essensoft.AspNetCore.Payment.QPay/IQPayNotifyClient.cs
  43. 32 54
      src/Essensoft.AspNetCore.Payment.QPay/QPayClient.cs
  44. 11 15
      src/Essensoft.AspNetCore.Payment.QPay/QPayNotifyClient.cs
  45. 0 5
      src/Essensoft.AspNetCore.Payment.QPay/QPayOptions.cs
  46. 28 1
      src/Essensoft.AspNetCore.Payment.QPay/ServiceCollectionExtensions.cs
  47. 0 57
      src/Essensoft.AspNetCore.Payment.QPay/Utility/HttpClientEx.cs
  48. 25 0
      src/Essensoft.AspNetCore.Payment.QPay/Utility/HttpClientUtility.cs
  49. 2 2
      src/Essensoft.AspNetCore.Payment.QPay/Utility/QPaySignature.cs
  50. 29 0
      src/Essensoft.AspNetCore.Payment.QPay/Utility/QPayUtility.cs
  51. 3 3
      src/Essensoft.AspNetCore.Payment.Security/AES_CTR_NoPadding.cs
  52. 1 1
      src/Essensoft.AspNetCore.Payment.Security/Essensoft.AspNetCore.Payment.Security.csproj
  53. 3 3
      src/Essensoft.AspNetCore.Payment.Security/MD5WithRSA.cs
  54. 3 3
      src/Essensoft.AspNetCore.Payment.Security/RSAUtilities.cs
  55. 3 3
      src/Essensoft.AspNetCore.Payment.Security/RSA_ECB_OAEPWithSHA1AndMGF1Padding.cs
  56. 3 3
      src/Essensoft.AspNetCore.Payment.Security/RSA_ECB_PKCS1Padding.cs
  57. 3 3
      src/Essensoft.AspNetCore.Payment.Security/RSA_NONE_PKCS1Padding.cs
  58. 3 3
      src/Essensoft.AspNetCore.Payment.Security/SHA1WithRSA.cs
  59. 3 3
      src/Essensoft.AspNetCore.Payment.Security/SHA256WithRSA.cs
  60. 3 3
      src/Essensoft.AspNetCore.Payment.Security/SM3.cs
  61. 2 1
      src/Essensoft.AspNetCore.Payment.UnionPay/Essensoft.AspNetCore.Payment.UnionPay.csproj
  62. 0 2
      src/Essensoft.AspNetCore.Payment.UnionPay/IUnionPayClient.cs
  63. 2 2
      src/Essensoft.AspNetCore.Payment.UnionPay/IUnionPayNotifyClient.cs
  64. 1 1
      src/Essensoft.AspNetCore.Payment.UnionPay/IUnionPayRequest.cs
  65. 8 1
      src/Essensoft.AspNetCore.Payment.UnionPay/ServiceCollectionExtensions.cs
  66. 36 44
      src/Essensoft.AspNetCore.Payment.UnionPay/UnionPayClient.cs
  67. 9 13
      src/Essensoft.AspNetCore.Payment.UnionPay/UnionPayNotifyClient.cs
  68. 0 82
      src/Essensoft.AspNetCore.Payment.UnionPay/Utility/HttpClientEx.cs
  69. 56 0
      src/Essensoft.AspNetCore.Payment.UnionPay/Utility/HttpClientUtility.cs
  70. 7 7
      src/Essensoft.AspNetCore.Payment.UnionPay/Utility/UnionPaySignature.cs
  71. 25 2
      src/Essensoft.AspNetCore.Payment.UnionPay/Utility/UnionPayUtility.cs
  72. 2 1
      src/Essensoft.AspNetCore.Payment.WeChatPay/Essensoft.AspNetCore.Payment.WeChatPay.csproj
  73. 0 2
      src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayClient.cs
  74. 2 2
      src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayNotifyClient.cs
  75. 28 1
      src/Essensoft.AspNetCore.Payment.WeChatPay/ServiceCollectionExtensions.cs
  76. 0 57
      src/Essensoft.AspNetCore.Payment.WeChatPay/Utility/HttpClientEx.cs
  77. 25 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Utility/HttpClientUtility.cs
  78. 2 2
      src/Essensoft.AspNetCore.Payment.WeChatPay/Utility/WeChatPaySignature.cs
  79. 25 2
      src/Essensoft.AspNetCore.Payment.WeChatPay/Utility/WeChatPayUtility.cs
  80. 36 63
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayClient.cs
  81. 10 14
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayNotifyClient.cs
  82. 0 5
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayOptions.cs

+ 26 - 5
README.MD

@@ -56,13 +56,34 @@ Essensoft.AspNetCore.Payment.UnionPay       | [![NuGet](https://img.shields.io/n
 
 * 银联支付 [开放平台](https://open.unionpay.com/ajweb/product)
 
-## 开发环境
-* Windows 10 1803 17134.48
-* VS2017 15.7.*
-* .NET Core SDK 2.1.* (x64)
+## 参考开发环境
+* Windows 10
+* VS2017 15.8.4
+* .NET Core SDK 2.1.402 (x64)
 
 ## 使用方式
-见示例项目:NewWebApplicationSample / WebApplicationSample</p>
+
+### 关于Payment使用 HttpClient 默认/证书 配置介绍 (目前仅WeChatPay\QPay部分API有使用到API证书配置) 
+
+* 以WeChatPay为例,有以下几种方式:
+1. 添加默认HttpClient供WeChatPayClient使用. (未使用到API证书时适用)
+`services.AddWeChatPayHttpClient();`
+
+2. 添加默认HttpClient和证书HttpClient供WeChatPayClient使用.
+
+* 根据证书路径
+`services.AddWeChatPayHttpClient(new X509Certificate2(Configuration["WeChatPay:Certificate"], Configuration["WeChatPay:MchId"], X509KeyStorageFlags.MachineKeySet));`
+
+* 根据证书Base64String
+`services.AddWeChatPayHttpClient(new X509Certificate2(Convert.FromBase64String(Configuration["WeChatPay:Certificate"]), Configuration["WeChatPay:MchId"], X509KeyStorageFlags.MachineKeySet));`
+
+3.  自行配置HttpClient供WeChatPayClient使用.
+```
+services.AddHttpClient(WeChatPayUtility.DefaultClientName);
+services.AddHttpClient(WeChatPayUtility.CertificateClientName).ConfigurePrimaryHttpMessageHandler(() => { ... });
+```
+
+详情见示例项目:NewWebApplicationSample / WebApplicationSample</p>
 
 <p align="center">
     <img src="http://p687qfgw0.bkt.clouddn.com/NewWebApplicationSample.png">

+ 1 - 1
samples/NewWebApplicationSample/NewWebApplicationSample.csproj

@@ -8,7 +8,7 @@
 
   <ItemGroup>
     <PackageReference Include="Microsoft.AspNetCore.App" />
-    <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.0" />
+    <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.1.4" />
   </ItemGroup>
 
   <ItemGroup>

+ 18 - 3
samples/NewWebApplicationSample/Startup.cs

@@ -1,4 +1,8 @@
-using Essensoft.AspNetCore.Payment.Alipay;
+using System;
+using System.Security.Cryptography.X509Certificates;
+using System.Text.Encodings.Web;
+using System.Text.Unicode;
+using Essensoft.AspNetCore.Payment.Alipay;
 using Essensoft.AspNetCore.Payment.JDPay;
 using Essensoft.AspNetCore.Payment.LianLianPay;
 using Essensoft.AspNetCore.Payment.QPay;
@@ -11,8 +15,6 @@ using Microsoft.AspNetCore.Mvc;
 using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.DependencyInjection;
 using Microsoft.Extensions.Logging;
-using System.Text.Encodings.Web;
-using System.Text.Unicode;
 
 namespace NewWebApplicationSample
 {
@@ -38,11 +40,24 @@ namespace NewWebApplicationSample
             services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
 
             services.AddAlipay();
+            services.AddAlipayHttpClient();
+
             services.AddJDPay();
+            services.AddJDPayHttpClient();
+
             services.AddQPay();
+            services.AddQPayHttpClient();
+            //services.AddQPayHttpClient(new X509Certificate2(Convert.FromBase64String(Configuration["QPay:Certificate"]), Configuration["QPay:MchId"], X509KeyStorageFlags.MachineKeySet));
+
             services.AddUnionPay();
+            services.AddUnionPayHttpClient();
+
             services.AddWeChatPay();
+            services.AddWeChatPayHttpClient();
+            //services.AddWeChatPayHttpClient(new X509Certificate2(Convert.FromBase64String(Configuration["WeChatPay:Certificate"]), Configuration["WeChatPay:MchId"], X509KeyStorageFlags.MachineKeySet));
+
             services.AddLianLianPay();
+            services.AddLianLianPayHttpClient();
 
             services.Configure<AlipayOptions>(Configuration.GetSection("Alipay"));
             services.Configure<JDPayOptions>(Configuration.GetSection("JDPay"));

+ 35 - 39
src/Essensoft.AspNetCore.Payment.Alipay/AlipayClient.cs

@@ -1,15 +1,16 @@
-using Essensoft.AspNetCore.Payment.Alipay.Parser;
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.Alipay.Parser;
 using Essensoft.AspNetCore.Payment.Alipay.Request;
 using Essensoft.AspNetCore.Payment.Alipay.Utility;
 using Essensoft.AspNetCore.Payment.Security;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
 using Newtonsoft.Json;
-using System;
-using System.Collections.Generic;
-using System.Security.Cryptography;
-using System.Text;
-using System.Threading.Tasks;
 
 namespace Essensoft.AspNetCore.Payment.Alipay
 {
@@ -39,22 +40,22 @@ namespace Essensoft.AspNetCore.Payment.Alipay
         private readonly RSAParameters PrivateRSAParameters;
         private readonly RSAParameters PublicRSAParameters;
 
-        public AlipayOptions Options { get; }
-
         public virtual ILogger Logger { get; set; }
 
-        protected internal HttpClientEx Client { get; set; }
+        public virtual IHttpClientFactory ClientFactory { get; set; }
+
+        public AlipayOptions Options { get; }
 
         #region AlipayClient Constructors
 
         public AlipayClient(
-            IOptions<AlipayOptions> optionsAccessor,
-            ILogger<AlipayClient> logger)
+            ILogger<AlipayClient> logger,
+            IHttpClientFactory clientFactory,
+            IOptions<AlipayOptions> optionsAccessor)
         {
-            Options = optionsAccessor.Value;
             Logger = logger;
-
-            Client = new HttpClientEx();
+            ClientFactory = clientFactory;
+            Options = optionsAccessor.Value;
 
             if (string.IsNullOrEmpty(Options.AppId))
             {
@@ -75,19 +76,6 @@ namespace Essensoft.AspNetCore.Payment.Alipay
             PublicRSAParameters = RSAUtilities.GetRSAParametersFormPublicKey(Options.RsaPublicKey);
         }
 
-        public AlipayClient(IOptions<AlipayOptions> optionsAccessor)
-            : this(optionsAccessor, null)
-        { }
-
-        #endregion
-
-        #region IAlipayClient Members
-
-        public void SetTimeout(int timeout)
-        {
-            Client.Timeout = new TimeSpan(0, 0, 0, timeout);
-        }
-
         #endregion
 
         #region IAlipayClient Members
@@ -159,7 +147,11 @@ namespace Essensoft.AspNetCore.Payment.Alipay
             if (request is IAlipayUploadRequest<T> uRequest)
             {
                 var fileParams = AlipayUtility.CleanupDictionary(uRequest.GetFileParameters());
-                body = await Client.DoPostAsync(Options.ServerUrl, txtParams, fileParams);
+
+                using (var client = ClientFactory.CreateClient(AlipayUtility.DefaultClientName))
+                {
+                    body = await HttpClientUtility.DoPostAsync(client, Options.ServerUrl, txtParams, fileParams);
+                }
             }
             else
             {
@@ -172,11 +164,11 @@ namespace Essensoft.AspNetCore.Payment.Alipay
                     {
                         if (tmpUrl.Contains("?"))
                         {
-                            tmpUrl = tmpUrl + "&" + HttpClientEx.BuildQuery(txtParams);
+                            tmpUrl = tmpUrl + "&" + AlipayUtility.BuildQuery(txtParams);
                         }
                         else
                         {
-                            tmpUrl = tmpUrl + "?" + HttpClientEx.BuildQuery(txtParams);
+                            tmpUrl = tmpUrl + "?" + AlipayUtility.BuildQuery(txtParams);
                         }
                     }
                     body = tmpUrl;
@@ -280,19 +272,23 @@ namespace Essensoft.AspNetCore.Payment.Alipay
             var signContent = AlipaySignature.GetSignContent(txtParams);
             txtParams.Add(SIGN, AlipaySignature.RSASignContent(signContent, PrivateRSAParameters, Options.SignType));
 
-            var query = HttpClientEx.BuildQuery(txtParams);
+            var query = AlipayUtility.BuildQuery(txtParams);
             Logger?.LogTrace(0, "Request:{query}", query);
 
             // 是否需要上传文件
             var body = string.Empty;
-            if (request is IAlipayUploadRequest<T> uRequest)
-            {
-                var fileParams = AlipayUtility.CleanupDictionary(uRequest.GetFileParameters());
-                body = await Client.DoPostAsync(Options.ServerUrl, txtParams, fileParams);
-            }
-            else
+            using (var client = ClientFactory.CreateClient(AlipayUtility.DefaultClientName))
             {
-                body = await Client.DoPostAsync(Options.ServerUrl, query);
+                if (request is IAlipayUploadRequest<T> uRequest)
+                {
+                    var fileParams = AlipayUtility.CleanupDictionary(uRequest.GetFileParameters());
+
+                    body = await HttpClientUtility.DoPostAsync(client, Options.ServerUrl, txtParams, fileParams);
+                }
+                else
+                {
+                    body = await HttpClientUtility.DoPostAsync(client, Options.ServerUrl, query);
+                }
             }
 
             Logger?.LogTrace(1, "Response:{body}", body);
@@ -414,7 +410,7 @@ namespace Essensoft.AspNetCore.Payment.Alipay
             sortedAlipayDic.Add(SIGN, signResult);
 
             // 参数拼接
-            var signedResult = HttpClientEx.BuildQuery(sortedAlipayDic);
+            var signedResult = AlipayUtility.BuildQuery(sortedAlipayDic);
 
             // 构造结果
             var rsp = Activator.CreateInstance<T>();

+ 12 - 16
src/Essensoft.AspNetCore.Payment.Alipay/AlipayNotifyClient.cs

@@ -1,14 +1,14 @@
-using Essensoft.AspNetCore.Payment.Alipay.Parser;
+using System;
+using System.Collections.Generic;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.Alipay.Parser;
 using Essensoft.AspNetCore.Payment.Alipay.Utility;
 using Essensoft.AspNetCore.Payment.Security;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
-using System;
-using System.Collections.Generic;
-using System.Security.Cryptography;
-using System.Text;
-using System.Threading.Tasks;
 
 namespace Essensoft.AspNetCore.Payment.Alipay
 {
@@ -16,18 +16,18 @@ namespace Essensoft.AspNetCore.Payment.Alipay
     {
         private readonly RSAParameters PublicRSAParameters;
 
-        public AlipayOptions Options { get; }
-
         public virtual ILogger Logger { get; set; }
 
+        public AlipayOptions Options { get; }
+
         #region AlipayNotifyClient Constructors
 
         public AlipayNotifyClient(
-            IOptions<AlipayOptions> optionsAccessor,
-            ILogger<AlipayNotifyClient> logger)
+            ILogger<AlipayNotifyClient> logger,
+            IOptions<AlipayOptions> optionsAccessor)
         {
-            Options = optionsAccessor.Value;
             Logger = logger;
+            Options = optionsAccessor.Value;
 
             if (string.IsNullOrEmpty(Options.RsaPublicKey))
             {
@@ -37,10 +37,6 @@ namespace Essensoft.AspNetCore.Payment.Alipay
             PublicRSAParameters = RSAUtilities.GetRSAParametersFormPublicKey(Options.RsaPublicKey);
         }
 
-        public AlipayNotifyClient(IOptions<AlipayOptions> optionsAccessor)
-            : this(optionsAccessor, null)
-        { }
-
         #endregion
 
         #region IAlipayNotifyClient Members
@@ -48,7 +44,7 @@ namespace Essensoft.AspNetCore.Payment.Alipay
         public async Task<T> ExecuteAsync<T>(HttpRequest request) where T : AlipayNotifyResponse
         {
             var parameters = await GetParametersAsync(request);
-            var query = HttpClientEx.BuildQuery(parameters);
+            var query = AlipayUtility.BuildQuery(parameters);
             Logger?.LogTrace(0, "Request:{query}", query);
 
             var parser = new AlipayDictionaryParser<T>();

+ 2 - 2
src/Essensoft.AspNetCore.Payment.Alipay/AlipayResponse.cs

@@ -1,6 +1,6 @@
-using Newtonsoft.Json;
-using System;
+using System;
 using System.Xml.Serialization;
+using Newtonsoft.Json;
 
 namespace Essensoft.AspNetCore.Payment.Alipay
 {

+ 2 - 1
src/Essensoft.AspNetCore.Payment.Alipay/Essensoft.AspNetCore.Payment.Alipay.csproj

@@ -18,7 +18,8 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.0" />
+    <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.2" />
+    <PackageReference Include="Microsoft.Extensions.Http" Version="2.1.1" />
     <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
   </ItemGroup>
 

+ 2 - 4
src/Essensoft.AspNetCore.Payment.Alipay/IAlipayClient.cs

@@ -1,5 +1,5 @@
-using Essensoft.AspNetCore.Payment.Alipay.Request;
-using System.Threading.Tasks;
+using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.Alipay.Request;
 
 namespace Essensoft.AspNetCore.Payment.Alipay
 {
@@ -8,8 +8,6 @@ namespace Essensoft.AspNetCore.Payment.Alipay
     /// </summary>
     public interface IAlipayClient
     {
-        void SetTimeout(int timeout);
-
         /// <summary>
         /// 执行Alipay公开API请求。
         /// </summary>

+ 2 - 2
src/Essensoft.AspNetCore.Payment.Alipay/IAlipayNotifyClient.cs

@@ -1,5 +1,5 @@
-using Microsoft.AspNetCore.Http;
-using System.Threading.Tasks;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
 
 namespace Essensoft.AspNetCore.Payment.Alipay
 {

+ 2 - 2
src/Essensoft.AspNetCore.Payment.Alipay/Parser/AlipayDictionaryParser.cs

@@ -1,6 +1,6 @@
-using Newtonsoft.Json;
-using System;
+using System;
 using System.Collections;
+using Newtonsoft.Json;
 
 namespace Essensoft.AspNetCore.Payment.Alipay.Parser
 {

+ 3 - 3
src/Essensoft.AspNetCore.Payment.Alipay/Parser/AlipayJsonParser.cs

@@ -1,9 +1,9 @@
-using Essensoft.AspNetCore.Payment.Alipay.Request;
+using System;
+using System.Collections;
+using Essensoft.AspNetCore.Payment.Alipay.Request;
 using Essensoft.AspNetCore.Payment.Alipay.Utility;
 using Essensoft.AspNetCore.Payment.Security;
 using Newtonsoft.Json;
-using System;
-using System.Collections;
 
 namespace Essensoft.AspNetCore.Payment.Alipay.Parser
 {

+ 4 - 4
src/Essensoft.AspNetCore.Payment.Alipay/Parser/AlipayXmlParser.cs

@@ -1,12 +1,12 @@
-using Essensoft.AspNetCore.Payment.Alipay.Request;
-using Essensoft.AspNetCore.Payment.Alipay.Utility;
-using Essensoft.AspNetCore.Payment.Security;
-using System;
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Text;
 using System.Text.RegularExpressions;
 using System.Xml.Serialization;
+using Essensoft.AspNetCore.Payment.Alipay.Request;
+using Essensoft.AspNetCore.Payment.Alipay.Utility;
+using Essensoft.AspNetCore.Payment.Security;
 
 namespace Essensoft.AspNetCore.Payment.Alipay.Parser
 {

+ 8 - 1
src/Essensoft.AspNetCore.Payment.Alipay/ServiceCollectionExtensions.cs

@@ -1,5 +1,6 @@
-using Essensoft.AspNetCore.Payment.Alipay;
 using System;
+using Essensoft.AspNetCore.Payment.Alipay;
+using Essensoft.AspNetCore.Payment.Alipay.Utility;
 
 namespace Microsoft.Extensions.DependencyInjection
 {
@@ -22,5 +23,11 @@ namespace Microsoft.Extensions.DependencyInjection
                 services.Configure(setupAction);
             }
         }
+
+        public static void AddAlipayHttpClient(
+            this IServiceCollection services)
+        {
+            services.AddHttpClient(AlipayUtility.DefaultClientName);
+        }
     }
 }

+ 27 - 0
src/Essensoft.AspNetCore.Payment.Alipay/Utility/AlipayUtility.cs

@@ -1,5 +1,7 @@
 using System;
 using System.Collections.Generic;
+using System.Net;
+using System.Text;
 
 namespace Essensoft.AspNetCore.Payment.Alipay.Utility
 {
@@ -8,6 +10,31 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Utility
     /// </summary>
     public static class AlipayUtility
     {
+        public static readonly string DefaultClientName = "Payment.Alipay.Client";
+
+        /// <summary>
+        /// 组装普通文本请求参数。
+        /// </summary>
+        /// <param name="parameters">Key-Value形式请求参数字典</param>
+        /// <returns>URL编码后的请求数据</returns>
+        public static string BuildQuery(IDictionary<string, string> parameters)
+        {
+            if (parameters == null || parameters.Count == 0)
+            {
+                throw new ArgumentNullException(nameof(parameters));
+            }
+
+            var content = new StringBuilder();
+            foreach (var iter in parameters)
+            {
+                if (!string.IsNullOrEmpty(iter.Value))
+                {
+                    content.Append(iter.Key + "=" + WebUtility.UrlEncode(iter.Value) + "&");
+                }
+            }
+            return content.ToString().Substring(0, content.Length - 1);
+        }
+
         /// <summary>
         /// 清除字典中值为空的项。
         /// </summary>

+ 13 - 43
src/Essensoft.AspNetCore.Payment.Alipay/Utility/HttpClientEx.cs → src/Essensoft.AspNetCore.Payment.Alipay/Utility/HttpClientUtility.cs

@@ -1,7 +1,6 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
-using System.Net;
 using System.Net.Http;
 using System.Text;
 using System.Threading.Tasks;
@@ -11,24 +10,18 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Utility
     /// <summary>
     /// 网络工具类。
     /// </summary>
-    public sealed class HttpClientEx : HttpClient
+    public static class HttpClientUtility
     {
-        public HttpClientEx() : base()
-        {
-            Timeout = new TimeSpan(0, 0, 0, 10);
-            DefaultRequestHeaders.Connection.Add("keep-alive");
-        }
-
         /// <summary>
         /// 执行HTTP POST请求。
         /// </summary>
         /// <param name="url">请求地址</param>
         /// <param name="parameters">请求参数</param>
         /// <returns>HTTP响应</returns>
-        public async Task<string> DoPostAsync(string url, IDictionary<string, string> parameters)
+        public static async Task<string> DoPostAsync(HttpClient client, string url, IDictionary<string, string> parameters)
         {
-            using (var requestContent = new StringContent(BuildQuery(parameters), Encoding.UTF8, "application/x-www-form-urlencoded"))
-            using (var response = await PostAsync(url, requestContent))
+            using (var requestContent = new StringContent(AlipayUtility.BuildQuery(parameters), Encoding.UTF8, "application/x-www-form-urlencoded"))
+            using (var response = await client.PostAsync(url, requestContent))
             using (var responseContent = response.Content)
             {
                 return await responseContent.ReadAsStringAsync();
@@ -41,10 +34,10 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Utility
         /// <param name="url">请求地址</param>
         /// <param name="content">请求内容</param>
         /// <returns>HTTP响应</returns>
-        public async Task<string> DoPostAsync(string url, string content)
+        public static async Task<string> DoPostAsync(HttpClient client, string url, string content)
         {
             using (var requestContent = new StringContent(content, Encoding.UTF8, "application/x-www-form-urlencoded"))
-            using (var response = await PostAsync(url, requestContent))
+            using (var response = await client.PostAsync(url, requestContent))
             using (var responseContent = response.Content)
             {
                 return await responseContent.ReadAsStringAsync();
@@ -57,21 +50,21 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Utility
         /// <param name="url">请求地址</param>
         /// <param name="parameters">请求参数</param>
         /// <returns>HTTP响应</returns>
-        public async Task<string> DoGetAsync(string url, IDictionary<string, string> parameters)
+        public static async Task<string> DoGetAsync(HttpClient client, string url, IDictionary<string, string> parameters)
         {
             if (parameters?.Count > 0)
             {
                 if (url.Contains("?"))
                 {
-                    url = url + "&" + BuildQuery(parameters);
+                    url = url + "&" + AlipayUtility.BuildQuery(parameters);
                 }
                 else
                 {
-                    url = url + "?" + BuildQuery(parameters);
+                    url = url + "?" + AlipayUtility.BuildQuery(parameters);
                 }
             }
 
-            using (var response = await GetAsync(url))
+            using (var response = await client.GetAsync(url))
             using (var responseContent = response.Content)
             {
                 return await responseContent.ReadAsStringAsync();
@@ -85,12 +78,12 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Utility
         /// <param name="textParams">请求文本参数</param>
         /// <param name="fileParams">请求文件参数</param>
         /// <returns>HTTP响应</returns>
-        public async Task<string> DoPostAsync(string url, IDictionary<string, string> textParams, IDictionary<string, FileItem> fileParams)
+        public static async Task<string> DoPostAsync(HttpClient client, string url, IDictionary<string, string> textParams, IDictionary<string, FileItem> fileParams)
         {
             // 如果没有文件参数,则走普通POST请求
             if (fileParams == null || fileParams.Count == 0)
             {
-                return await DoPostAsync(url, textParams);
+                return await DoPostAsync(client, url, textParams);
             }
 
             var boundary = DateTime.Now.Ticks.ToString("X"); // 随机分隔线
@@ -127,34 +120,11 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Utility
             reqStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length);
 
             using (var requestContent = new StringContent(reqStream.ToString(), Encoding.UTF8, "multipart/form-data;boundary=" + boundary))
-            using (var response = await PostAsync(url, requestContent))
+            using (var response = await client.PostAsync(url, requestContent))
             using (var responseContent = response.Content)
             {
                 return await responseContent.ReadAsStringAsync();
             }
         }
-
-        /// <summary>
-        /// 组装普通文本请求参数。
-        /// </summary>
-        /// <param name="parameters">Key-Value形式请求参数字典</param>
-        /// <returns>URL编码后的请求数据</returns>
-        public static string BuildQuery(IDictionary<string, string> parameters)
-        {
-            if (parameters == null || parameters.Count == 0)
-            {
-                throw new ArgumentNullException(nameof(parameters));
-            }
-
-            var content = new StringBuilder();
-            foreach (var iter in parameters)
-            {
-                if (!string.IsNullOrEmpty(iter.Value))
-                {
-                    content.Append(iter.Key + "=" + WebUtility.UrlEncode(iter.Value) + "&");
-                }
-            }
-            return content.ToString().Substring(0, content.Length - 1);
-        }
     }
 }

+ 2 - 1
src/Essensoft.AspNetCore.Payment.JDPay/Essensoft.AspNetCore.Payment.JDPay.csproj

@@ -18,7 +18,8 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.0" />
+    <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.2" />
+    <PackageReference Include="Microsoft.Extensions.Http" Version="2.1.1" />
     <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
   </ItemGroup>
   

+ 0 - 2
src/Essensoft.AspNetCore.Payment.JDPay/IJDPayClient.cs

@@ -4,8 +4,6 @@ namespace Essensoft.AspNetCore.Payment.JDPay
 {
     public interface IJDPayClient
     {
-        void SetTimeout(int timeout);
-
         /// <summary>
         /// 执行JDPay API请求。
         /// </summary>

+ 2 - 2
src/Essensoft.AspNetCore.Payment.JDPay/IJDPayNotifyClient.cs

@@ -1,5 +1,5 @@
-using Microsoft.AspNetCore.Http;
-using System.Threading.Tasks;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
 
 namespace Essensoft.AspNetCore.Payment.JDPay
 {

+ 77 - 83
src/Essensoft.AspNetCore.Payment.JDPay/JDPayClient.cs

@@ -1,4 +1,10 @@
-using Essensoft.AspNetCore.Payment.JDPay.Parser;
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+using Essensoft.AspNetCore.Payment.JDPay.Parser;
 using Essensoft.AspNetCore.Payment.JDPay.Request;
 using Essensoft.AspNetCore.Payment.JDPay.Utility;
 using Essensoft.AspNetCore.Payment.Security;
@@ -6,11 +12,6 @@ using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
 using Newtonsoft.Json;
 using Org.BouncyCastle.Crypto;
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Threading.Tasks;
-using System.Xml;
 
 namespace Essensoft.AspNetCore.Payment.JDPay
 {
@@ -20,21 +21,22 @@ namespace Essensoft.AspNetCore.Payment.JDPay
         private readonly AsymmetricKeyParameter PublicKey;
         private readonly byte[] DesKey;
 
-        public JDPayOptions Options { get; }
-
         public virtual ILogger Logger { get; set; }
 
-        protected internal HttpClientEx Client { get; set; }
+        public virtual IHttpClientFactory ClientFactory { get; set; }
+
+        public JDPayOptions Options { get; }
 
         #region JDPayClient Constructors
 
         public JDPayClient(
             IOptions<JDPayOptions> optionsAccessor,
-            ILogger<JDPayClient> logger)
+            ILogger<JDPayClient> logger,
+            IHttpClientFactory clientFactory)
         {
-            Options = optionsAccessor.Value;
             Logger = logger;
-            Client = new HttpClientEx();
+            ClientFactory = clientFactory;
+            Options = optionsAccessor.Value;
 
             if (string.IsNullOrEmpty(Options.Merchant))
             {
@@ -61,19 +63,6 @@ namespace Essensoft.AspNetCore.Payment.JDPay
             DesKey = Convert.FromBase64String(Options.DesKey);
         }
 
-        public JDPayClient(IOptions<JDPayOptions> optionsAccessor)
-           : this(optionsAccessor, null)
-        { }
-
-        #endregion
-
-        #region IJDPayClient Members
-
-        public void SetTimeout(int timeout)
-        {
-            Client.Timeout = new TimeSpan(0, 0, 0, timeout);
-        }
-
         #endregion
 
         #region IJDPayClient Members
@@ -86,46 +75,49 @@ namespace Essensoft.AspNetCore.Payment.JDPay
             var content = BuildEncryptXml(request, sortedTxtParams);
             Logger?.LogTrace(0, "Request:{content}", content);
 
-            var body = await Client.DoPostAsync(request.GetRequestUrl(), content);
-            Logger?.LogTrace(1, "Response:{content}", body);
-
-            var parser = new JDPayXmlParser<T>();
-            var rsp = parser.Parse(JDPayUtility.FotmatXmlString(body));
-            if (!string.IsNullOrEmpty(rsp.Encrypt))
+            using (var client = ClientFactory.CreateClient(JDPayUtility.DefaultClientName))
             {
-                var encrypt = rsp.Encrypt;
-                var base64EncryptStr = Encoding.UTF8.GetString(Convert.FromBase64String(encrypt));
-                var reqBody = JDPaySecurity.DecryptECB(base64EncryptStr, DesKey);
-                Logger?.LogTrace(2, "Encrypt Content:{body}", reqBody);
-
-                var reqBodyDoc = new XmlDocument() { XmlResolver = null };
-                reqBodyDoc.LoadXml(reqBody);
-
-                var sign = JDPayUtility.GetValue(reqBodyDoc, "sign");
-                var rootNode = reqBodyDoc.SelectSingleNode("jdpay");
-                var signNode = rootNode.SelectSingleNode("sign");
-                rootNode.RemoveChild(signNode);
-
-                var reqBodyStr = JDPayUtility.ConvertXmlToString(reqBodyDoc);
-                var xmlh = rsp.Body.Substring(0, rsp.Body.IndexOf("<jdpay>"));
-                if (!string.IsNullOrEmpty(xmlh))
-                {
-                    reqBodyStr = reqBodyStr.Replace("<?xml version=\"1.0\" encoding=\"UTF-8\"?>", xmlh);
-                }
-                var sha256SourceSignString = SHA256.Compute(reqBodyStr);
-                var decryptByte = RSA_ECB_PKCS1Padding.Decrypt(Convert.FromBase64String(sign), PublicKey);
-                var decryptStr = JDPaySecurity.BytesToString(decryptByte);
-                if (sha256SourceSignString == decryptStr)
-                {
-                    rsp = parser.Parse(reqBody);
-                    rsp.Encrypt = encrypt;
-                }
-                else
+                var body = await HttpClientUtility.DoPostAsync(client, request.GetRequestUrl(), content);
+                Logger?.LogTrace(1, "Response:{content}", body);
+
+                var parser = new JDPayXmlParser<T>();
+                var rsp = parser.Parse(JDPayUtility.FotmatXmlString(body));
+                if (!string.IsNullOrEmpty(rsp.Encrypt))
                 {
-                    throw new Exception("sign check fail: check Sign and Data Fail!");
+                    var encrypt = rsp.Encrypt;
+                    var base64EncryptStr = Encoding.UTF8.GetString(Convert.FromBase64String(encrypt));
+                    var reqBody = JDPaySecurity.DecryptECB(base64EncryptStr, DesKey);
+                    Logger?.LogTrace(2, "Encrypt Content:{body}", reqBody);
+
+                    var reqBodyDoc = new XmlDocument() { XmlResolver = null };
+                    reqBodyDoc.LoadXml(reqBody);
+
+                    var sign = JDPayUtility.GetValue(reqBodyDoc, "sign");
+                    var rootNode = reqBodyDoc.SelectSingleNode("jdpay");
+                    var signNode = rootNode.SelectSingleNode("sign");
+                    rootNode.RemoveChild(signNode);
+
+                    var reqBodyStr = JDPayUtility.ConvertXmlToString(reqBodyDoc);
+                    var xmlh = rsp.Body.Substring(0, rsp.Body.IndexOf("<jdpay>"));
+                    if (!string.IsNullOrEmpty(xmlh))
+                    {
+                        reqBodyStr = reqBodyStr.Replace("<?xml version=\"1.0\" encoding=\"UTF-8\"?>", xmlh);
+                    }
+                    var sha256SourceSignString = SHA256.Compute(reqBodyStr);
+                    var decryptByte = RSA_ECB_PKCS1Padding.Decrypt(Convert.FromBase64String(sign), PublicKey);
+                    var decryptStr = JDPaySecurity.BytesToString(decryptByte);
+                    if (sha256SourceSignString == decryptStr)
+                    {
+                        rsp = parser.Parse(reqBody);
+                        rsp.Encrypt = encrypt;
+                    }
+                    else
+                    {
+                        throw new Exception("sign check fail: check Sign and Data Fail!");
+                    }
                 }
+                return rsp;
             }
-            return rsp;
         }
 
         #endregion
@@ -152,8 +144,8 @@ namespace Essensoft.AspNetCore.Payment.JDPay
         {
             var sortedTxtParams = new JDPayDictionary(request.GetParameters())
             {
-                { Contants.CUSTOMER_NO, Options.CustomerNo },
-                { Contants.SIGN_TYPE, Options.SignType }
+                { JDPayContants.CUSTOMER_NO, Options.CustomerNo },
+                { JDPayContants.SIGN_TYPE, Options.SignType }
             };
 
             var isEncrypt = false;
@@ -165,24 +157,26 @@ namespace Essensoft.AspNetCore.Payment.JDPay
 
             var encryptDic = JDPaySecurity.EncryptData(Options.PrivateCret, Options.Password, Options.PublicCert, sortedTxtParams, Options.SingKey, Options.EncryptType, isEncrypt);
 
-            var content = HttpClientEx.BuildQuery(encryptDic);
+            var content = JDPayUtility.BuildQuery(encryptDic);
             Logger?.LogTrace(0, "Request:{content}", content);
 
-            var body = await Client.DoPostAsync(request.GetRequestUrl(), content, "application/x-www-form-urlencoded");
-            Logger?.LogTrace(1, "Response:{content}", body);
+            using (var client = ClientFactory.CreateClient(JDPayUtility.DefaultClientName))
+            {
+                var body = await HttpClientUtility.DoPostAsync(client, request.GetRequestUrl(), content, "application/x-www-form-urlencoded");
+                Logger?.LogTrace(1, "Response:{content}", body);
 
-            var rsp = JsonConvert.DeserializeObject<T>(body);
+                var rsp = JsonConvert.DeserializeObject<T>(body);
 
-            // 验签
-            var dic = JsonConvert.DeserializeObject<JDPayDictionary>(body);
+                // 验签
+                var dic = JsonConvert.DeserializeObject<JDPayDictionary>(body);
+                if (!JDPaySecurity.VerifySign(dic, Options.SingKey))
+                {
+                    throw new Exception("sign check fail: check Sign and Data Fail!");
+                }
 
-            if (!JDPaySecurity.VerifySign(dic, Options.SingKey))
-            {
-                throw new Exception("sign check fail: check Sign and Data Fail!");
+                rsp.Body = body;
+                return rsp;
             }
-
-            rsp.Body = body;
-            return rsp;
         }
 
         #endregion
@@ -201,9 +195,9 @@ namespace Essensoft.AspNetCore.Payment.JDPay
             // 字典排序
             var reqdic = new JDPayDictionary
             {
-                { Contants.VERSION, request.GetApiVersion() },
-                { Contants.MERCHANT, Options.Merchant },
-                { Contants.ENCRYPT, Convert.ToBase64String(Encoding.UTF8.GetBytes(encrypt)) }
+                { JDPayContants.VERSION, request.GetApiVersion() },
+                { JDPayContants.MERCHANT, Options.Merchant },
+                { JDPayContants.ENCRYPT, Convert.ToBase64String(Encoding.UTF8.GetBytes(encrypt)) }
             };
 
             return JDPayUtility.SortedDictionary2XmlStr(reqdic);
@@ -213,8 +207,8 @@ namespace Essensoft.AspNetCore.Payment.JDPay
         {
             var signDic = new JDPayDictionary(parameters)
             {
-                { Contants.VERSION, request.GetApiVersion() },
-                { Contants.MERCHANT, Options.Merchant },
+                { JDPayContants.VERSION, request.GetApiVersion() },
+                { JDPayContants.MERCHANT, Options.Merchant },
             };
 
             var signContent = JDPaySecurity.GetSignContent(signDic);
@@ -222,9 +216,9 @@ namespace Essensoft.AspNetCore.Payment.JDPay
 
             var encyptDic = new JDPayDictionary
             {
-                { Contants.VERSION, request.GetApiVersion() },
-                { Contants.MERCHANT, Options.Merchant },
-                { Contants.SIGN, sign }
+                { JDPayContants.VERSION, request.GetApiVersion() },
+                { JDPayContants.MERCHANT, Options.Merchant },
+                { JDPayContants.SIGN, sign }
             };
 
             foreach (var iter in parameters)

+ 16 - 23
src/Essensoft.AspNetCore.Payment.JDPay/JDPayNotifyClient.cs

@@ -1,40 +1,37 @@
-using Essensoft.AspNetCore.Payment.JDPay.Notify;
+using System;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+using Essensoft.AspNetCore.Payment.JDPay.Notify;
 using Essensoft.AspNetCore.Payment.JDPay.Parser;
-using Essensoft.AspNetCore.Payment.JDPay.Response;
 using Essensoft.AspNetCore.Payment.JDPay.Utility;
 using Essensoft.AspNetCore.Payment.Security;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
 using Org.BouncyCastle.Crypto;
-using System;
-using System.IO;
-using System.Text;
-using System.Threading.Tasks;
-using System.Xml;
 
 namespace Essensoft.AspNetCore.Payment.JDPay
 {
     public class JDPayNotifyClient : IJDPayNotifyClient
     {
-        private const string SIGN = "sign";
-
         private readonly AsymmetricKeyParameter PrivateKey;
         private readonly AsymmetricKeyParameter PublicKey;
         private readonly byte[] DesKey;
 
-        public JDPayOptions Options { get; }
-
         public virtual ILogger Logger { get; set; }
 
+        public JDPayOptions Options { get; }
+
         #region JDPayNotifyClient Constructors
 
         public JDPayNotifyClient(
-            IOptions<JDPayOptions> optionsAccessor,
-            ILogger<JDPayNotifyClient> logger)
+            ILogger<JDPayNotifyClient> logger,
+            IOptions<JDPayOptions> optionsAccessor)
         {
-            Options = optionsAccessor.Value;
             Logger = logger;
+            Options = optionsAccessor.Value;
 
             if (string.IsNullOrEmpty(Options.Merchant))
             {
@@ -61,10 +58,6 @@ namespace Essensoft.AspNetCore.Payment.JDPay
             DesKey = Convert.FromBase64String(Options.DesKey);
         }
 
-        public JDPayNotifyClient(IOptions<JDPayOptions> optionsAccessor)
-            : this(optionsAccessor, null)
-        { }
-
         #endregion
 
         #region IJDPayNotifyClient Members
@@ -77,7 +70,7 @@ namespace Essensoft.AspNetCore.Payment.JDPay
 
                 var parameters = GetParameters(request, !(rspInstance is JDPayDefrayPayNotifyResponse));
 
-                var query = HttpClientEx.BuildQuery(parameters);
+                var query = JDPayUtility.BuildQuery(parameters);
                 Logger?.LogTrace(0, "Request:{query}", query);
 
                 var parser = new JDPayDictionaryParser<T>();
@@ -164,7 +157,7 @@ namespace Essensoft.AspNetCore.Payment.JDPay
                         var value = iter.Value.ToString();
                         if (isDecrypt)
                         {
-                            value = iter.Key == SIGN ? iter.Value.ToString() : JDPaySecurity.DecryptECB(iter.Value, DesKey);
+                            value = iter.Key == JDPayContants.SIGN ? iter.Value.ToString() : JDPaySecurity.DecryptECB(iter.Value, DesKey);
                         }
                         parameters.Add(iter.Key, value);
                     }
@@ -179,7 +172,7 @@ namespace Essensoft.AspNetCore.Payment.JDPay
                         var value = iter.Value.ToString();
                         if (isDecrypt)
                         {
-                            value = iter.Key == SIGN ? iter.Value.ToString() : JDPaySecurity.DecryptECB(iter.Value, DesKey);
+                            value = iter.Key == JDPayContants.SIGN ? iter.Value.ToString() : JDPaySecurity.DecryptECB(iter.Value, DesKey);
                         }
                         parameters.Add(iter.Key, value);
                     }
@@ -195,7 +188,7 @@ namespace Essensoft.AspNetCore.Payment.JDPay
                 throw new Exception("sign check fail: parameters is Empty!");
             }
 
-            if (!parameters.TryGetValue(Contants.SIGN, out var sign))
+            if (!parameters.TryGetValue(JDPayContants.SIGN, out var sign))
             {
                 throw new Exception("sign check fail: sign is Empty!");
             }
@@ -214,7 +207,7 @@ namespace Essensoft.AspNetCore.Payment.JDPay
                 throw new Exception("sign check fail: parameters is Empty!");
             }
 
-            if (!parameters.TryGetValue(Contants.SIGN_DATA, out var sign_data))
+            if (!parameters.TryGetValue(JDPayContants.SIGN_DATA, out var sign_data))
             {
                 throw new Exception("sign check fail: sign is Empty!");
             }

+ 2 - 2
src/Essensoft.AspNetCore.Payment.JDPay/Parser/JDPayDictionaryParser.cs

@@ -1,9 +1,9 @@
-using Essensoft.AspNetCore.Payment.JDPay.Utility;
-using System;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Reflection;
 using System.Xml.Serialization;
+using Essensoft.AspNetCore.Payment.JDPay.Utility;
 
 namespace Essensoft.AspNetCore.Payment.JDPay.Parser
 {

+ 8 - 1
src/Essensoft.AspNetCore.Payment.JDPay/ServiceCollectionExtensions.cs

@@ -1,5 +1,6 @@
-using Essensoft.AspNetCore.Payment.JDPay;
 using System;
+using Essensoft.AspNetCore.Payment.JDPay;
+using Essensoft.AspNetCore.Payment.JDPay.Utility;
 
 namespace Microsoft.Extensions.DependencyInjection
 {
@@ -22,5 +23,11 @@ namespace Microsoft.Extensions.DependencyInjection
                 services.Configure(setupAction);
             }
         }
+
+        public static void AddJDPayHttpClient(
+            this IServiceCollection services)
+        {
+            services.AddHttpClient(JDPayUtility.DefaultClientName);
+        }
     }
 }

+ 0 - 52
src/Essensoft.AspNetCore.Payment.JDPay/Utility/HttpClientEx.cs

@@ -1,52 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Net;
-using System.Net.Http;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Essensoft.AspNetCore.Payment.JDPay.Utility
-{
-    public sealed class HttpClientEx : HttpClient
-    {
-        public HttpClientEx() : base()
-        {
-            Timeout = new TimeSpan(0, 0, 0, 10);
-            DefaultRequestHeaders.Connection.Add("keep-alive");
-        }
-
-        /// <summary>
-        /// 执行HTTP POST请求。
-        /// </summary>
-        /// <param name="url">请求地址</param>
-        /// <param name="content">请求内容</param>
-        /// <returns>HTTP响应</returns>
-        public async Task<string> DoPostAsync(string url, string content, string mediaType = "application/xml")
-        {
-            using (var requestContent = new StringContent(content, Encoding.UTF8, mediaType))
-            using (var response = await PostAsync(url, requestContent))
-            using (var resContent = response.Content)
-            {
-                return await resContent.ReadAsStringAsync();
-            }
-        }
-
-        /// <summary>
-        /// 组装普通文本请求参数。
-        /// </summary>
-        /// <param name="parameters">Key-Value形式请求参数字典</param>
-        /// <returns>URL编码后的请求数据</returns>
-        public static string BuildQuery(IDictionary<string, string> parameters)
-        {
-            var content = new StringBuilder();
-            foreach (var iter in parameters)
-            {
-                if (!string.IsNullOrEmpty(iter.Value))
-                {
-                    content.Append(iter.Key + "=" + WebUtility.UrlEncode(iter.Value) + "&");
-                }
-            }
-            return content.ToString().Substring(0, content.Length - 1);
-        }
-    }
-}

+ 25 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Utility/HttpClientUtility.cs

@@ -0,0 +1,25 @@
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Utility
+{
+    public static class HttpClientUtility
+    {
+        /// <summary>
+        /// 执行HTTP POST请求。
+        /// </summary>
+        /// <param name="url">请求地址</param>
+        /// <param name="content">请求内容</param>
+        /// <returns>HTTP响应</returns>
+        public static async Task<string> DoPostAsync(HttpClient client, string url, string content, string mediaType = "application/xml")
+        {
+            using (var requestContent = new StringContent(content, Encoding.UTF8, mediaType))
+            using (var response = await client.PostAsync(url, requestContent))
+            using (var resContent = response.Content)
+            {
+                return await resContent.ReadAsStringAsync();
+            }
+        }
+    }
+}

+ 1 - 1
src/Essensoft.AspNetCore.Payment.JDPay/Utility/Contants.cs → src/Essensoft.AspNetCore.Payment.JDPay/Utility/JDPayContants.cs

@@ -1,6 +1,6 @@
 namespace Essensoft.AspNetCore.Payment.JDPay.Utility
 {
-    public class Contants
+    public class JDPayContants
     {
         public const string VERSION = "version";
         public const string MERCHANT = "merchant";

+ 18 - 18
src/Essensoft.AspNetCore.Payment.JDPay/Utility/JDPaySecurity.cs

@@ -1,15 +1,15 @@
-using Essensoft.AspNetCore.Payment.Security;
+using System;
+using System.Collections;
+using System.IO;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using Essensoft.AspNetCore.Payment.Security;
 using Org.BouncyCastle.Cms;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Operators;
 using Org.BouncyCastle.Pkcs;
 using Org.BouncyCastle.Security;
 using Org.BouncyCastle.X509.Store;
-using System;
-using System.Collections;
-using System.IO;
-using System.Security.Cryptography.X509Certificates;
-using System.Text;
 using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
 
 namespace Essensoft.AspNetCore.Payment.JDPay.Utility
@@ -254,10 +254,10 @@ namespace Essensoft.AspNetCore.Payment.JDPay.Utility
 
         public static bool VerifySign(JDPayDictionary dic, string key)
         {
-            dic.TryGetValue(Contants.SIGN_TYPE, out var algorithm);
-            dic.TryGetValue(Contants.SIGN_DATA, out var sign);
-            dic.Remove(Contants.SIGN_TYPE);
-            dic.Remove(Contants.SIGN_DATA);
+            dic.TryGetValue(JDPayContants.SIGN_TYPE, out var algorithm);
+            dic.TryGetValue(JDPayContants.SIGN_DATA, out var sign);
+            dic.Remove(JDPayContants.SIGN_TYPE);
+            dic.Remove(JDPayContants.SIGN_DATA);
             return Verify(sign, dic, algorithm, key);
         }
 
@@ -276,23 +276,23 @@ namespace Essensoft.AspNetCore.Payment.JDPay.Utility
             var encryptData = new JDPayDictionary();
             var data = GetNPP10SignContentOrEncryptContent(dic);
 
-            dic.TryGetValue(Contants.CUSTOMER_NO, out var customerNo);
-            dic.TryGetValue(Contants.SIGN_TYPE, out var signType);
+            dic.TryGetValue(JDPayContants.CUSTOMER_NO, out var customerNo);
+            dic.TryGetValue(JDPayContants.SIGN_TYPE, out var signType);
 
             if (!isEncrypt || string.IsNullOrEmpty(encryptType))
             {
-                dic.Add(Contants.SIGN_DATA, GetNPP10Sign(data, signType, singKey));
+                dic.Add(JDPayContants.SIGN_DATA, GetNPP10Sign(data, signType, singKey));
                 encryptData = dic;
             }
             else
             {
-                encryptData.Add(Contants.SIGN_TYPE, signType);
-                encryptData.Add(Contants.SIGN_DATA, GetNPP10Sign(data, signType, singKey));
-                encryptData.Add(Contants.CUSTOMER_NO, customerNo);
-                encryptData.Add(Contants.ENCRYPT_TYPE, encryptType);
+                encryptData.Add(JDPayContants.SIGN_TYPE, signType);
+                encryptData.Add(JDPayContants.SIGN_DATA, GetNPP10Sign(data, signType, singKey));
+                encryptData.Add(JDPayContants.CUSTOMER_NO, customerNo);
+                encryptData.Add(JDPayContants.ENCRYPT_TYPE, encryptType);
                 if ("RSA" == encryptType)
                 {
-                    encryptData.Add(Contants.ENCRYPT_DATA, SignEnvelop(signCert, password, envelopCert, data));
+                    encryptData.Add(JDPayContants.ENCRYPT_DATA, SignEnvelop(signCert, password, envelopCert, data));
                 }
                 else
                 {

+ 24 - 2
src/Essensoft.AspNetCore.Payment.JDPay/Utility/JDPayUtility.cs

@@ -1,17 +1,39 @@
-using Microsoft.AspNetCore.Http;
-using System;
+using System;
+using System.Collections.Generic;
 using System.ComponentModel;
 using System.IO;
 using System.Linq.Expressions;
+using System.Net;
 using System.Net.Http.Headers;
 using System.Text;
 using System.Text.RegularExpressions;
 using System.Xml;
+using Microsoft.AspNetCore.Http;
 
 namespace Essensoft.AspNetCore.Payment.JDPay.Utility
 {
     public static class JDPayUtility
     {
+        public static readonly string DefaultClientName = "Payment.JDPay.Client";
+
+        /// <summary>
+        /// 组装普通文本请求参数。
+        /// </summary>
+        /// <param name="parameters">Key-Value形式请求参数字典</param>
+        /// <returns>URL编码后的请求数据</returns>
+        public static string BuildQuery(IDictionary<string, string> parameters)
+        {
+            var content = new StringBuilder();
+            foreach (var iter in parameters)
+            {
+                if (!string.IsNullOrEmpty(iter.Value))
+                {
+                    content.Append(iter.Key + "=" + WebUtility.UrlEncode(iter.Value) + "&");
+                }
+            }
+            return content.ToString().Substring(0, content.Length - 1);
+        }
+
         internal static bool HasTextXmlContentType(this HttpRequest request)
         {
             // Content-Type: text/xml

+ 2 - 1
src/Essensoft.AspNetCore.Payment.LianLianPay/Essensoft.AspNetCore.Payment.LianLianPay.csproj

@@ -18,7 +18,8 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.0" />
+    <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.2" />
+    <PackageReference Include="Microsoft.Extensions.Http" Version="2.1.1" />
     <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
   </ItemGroup>
 

+ 0 - 2
src/Essensoft.AspNetCore.Payment.LianLianPay/ILianLianPayClient.cs

@@ -4,8 +4,6 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
 {
     public interface ILianLianPayClient
     {
-        void SetTimeout(int timeout);
-
         /// <summary>
         /// 执行LianLianPay API请求。
         /// </summary>

+ 2 - 2
src/Essensoft.AspNetCore.Payment.LianLianPay/ILianLianPayNotifyClient.cs

@@ -1,5 +1,5 @@
-using Microsoft.AspNetCore.Http;
-using System.Threading.Tasks;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
 
 namespace Essensoft.AspNetCore.Payment.LianLianPay
 {

+ 36 - 45
src/Essensoft.AspNetCore.Payment.LianLianPay/LianLianPayClient.cs

@@ -1,4 +1,9 @@
-using Essensoft.AspNetCore.Payment.LianLianPay.Parser;
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.LianLianPay.Parser;
 using Essensoft.AspNetCore.Payment.LianLianPay.Request;
 using Essensoft.AspNetCore.Payment.LianLianPay.Utility;
 using Essensoft.AspNetCore.Payment.Security;
@@ -6,10 +11,6 @@ using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
 using Newtonsoft.Json;
 using Org.BouncyCastle.Crypto;
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Threading.Tasks;
 
 namespace Essensoft.AspNetCore.Payment.LianLianPay
 {
@@ -25,22 +26,22 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
         private readonly AsymmetricKeyParameter PrivateKey;
         private readonly AsymmetricKeyParameter PublicKey;
 
-        public LianLianPayOptions Options { get; }
-
         public virtual ILogger Logger { get; set; }
 
-        protected internal HttpClientEx Client { get; set; }
+        public virtual IHttpClientFactory ClientFactory { get; set; }
+
+        public LianLianPayOptions Options { get; }
 
         #region LianLianPayClient Constructors
 
         public LianLianPayClient(
-            IOptions<LianLianPayOptions> optionsAccessor,
-            ILogger<LianLianPayClient> logger)
+            ILogger<LianLianPayClient> logger,
+            IHttpClientFactory clientFactory,
+            IOptions<LianLianPayOptions> optionsAccessor)
         {
-            Options = optionsAccessor.Value;
             Logger = logger;
-
-            Client = new HttpClientEx();
+            ClientFactory = clientFactory;
+            Options = optionsAccessor.Value;
 
             if (string.IsNullOrEmpty(Options.OidPartner))
             {
@@ -66,19 +67,6 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
             PublicKey = RSAUtilities.GetKeyParameterFormPublicKey(Options.RsaPublicKey);
         }
 
-        public LianLianPayClient(IOptions<LianLianPayOptions> optionsAccessor)
-            : this(optionsAccessor, null)
-        { }
-
-        #endregion
-
-        #region ILianLianPayClient Members
-
-        public void SetTimeout(int timeout)
-        {
-            Client.Timeout = new TimeSpan(0, 0, 0, timeout);
-        }
-
         #endregion
 
         #region ILianLianPayClient Members
@@ -109,26 +97,29 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
             }
             Logger?.LogTrace(0, "Request:{content}", content);
 
-            var body = await Client.DoPostAsync(request.GetRequestUrl(), content);
-            Logger?.LogTrace(1, "Response:{body}", body);
+            using (var client = ClientFactory.CreateClient(LianLianPayUtility.DefaultClientName))
+            {
+                var body = await HttpClientUtility.DoPostAsync(client, request.GetRequestUrl(), content);
+                Logger?.LogTrace(1, "Response:{body}", body);
 
-            var parser = new LianLianPayJsonParser<T>();
-            var rsp = parser.Parse(body);
+                var parser = new LianLianPayJsonParser<T>();
+                var rsp = parser.Parse(body);
 
-            // 不签名参数
-            var excludePara = new List<string>();
-            if (request is LianLianPayOrderQueryRequest)
-            {
-                excludePara.Add("bank_name");
-                excludePara.Add("card_no");
-            }
-            else if (request is LianLianPayQueryBankCarBindListRequest)
-            {
-                excludePara.Add("agreement_list");
-            }
+                // 不签名参数
+                var excludePara = new List<string>();
+                if (request is LianLianPayOrderQueryRequest)
+                {
+                    excludePara.Add("bank_name");
+                    excludePara.Add("card_no");
+                }
+                else if (request is LianLianPayQueryBankCarBindListRequest)
+                {
+                    excludePara.Add("agreement_list");
+                }
 
-            CheckNotifySign(rsp.Parameters, excludePara);
-            return rsp;
+                CheckNotifySign(rsp.Parameters, excludePara);
+                return rsp;
+            }
         }
 
         #endregion
@@ -164,11 +155,11 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
                 {
                     if (tmpUrl.Contains("?"))
                     {
-                        tmpUrl = tmpUrl + "&" + HttpClientEx.BuildQuery(txtParams);
+                        tmpUrl = tmpUrl + "&" + LianLianPayUtility.BuildQuery(txtParams);
                     }
                     else
                     {
-                        tmpUrl = tmpUrl + "?" + HttpClientEx.BuildQuery(txtParams);
+                        tmpUrl = tmpUrl + "?" + LianLianPayUtility.BuildQuery(txtParams);
                     }
                 }
                 body = tmpUrl;

+ 10 - 14
src/Essensoft.AspNetCore.Payment.LianLianPay/LianLianPayNotifyClient.cs

@@ -1,13 +1,13 @@
-using Essensoft.AspNetCore.Payment.LianLianPay.Parser;
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.LianLianPay.Parser;
 using Essensoft.AspNetCore.Payment.LianLianPay.Utility;
 using Essensoft.AspNetCore.Payment.Security;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
 using Org.BouncyCastle.Crypto;
-using System;
-using System.IO;
-using System.Threading.Tasks;
 
 namespace Essensoft.AspNetCore.Payment.LianLianPay
 {
@@ -15,18 +15,18 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
     {
         private readonly AsymmetricKeyParameter PublicKey;
 
-        public LianLianPayOptions Options { get; set; }
-
         public virtual ILogger Logger { get; set; }
 
+        public LianLianPayOptions Options { get; }
+
         #region LianLianPayNotifyClient Constructors
 
         public LianLianPayNotifyClient(
-            IOptions<LianLianPayOptions> optionsAccessor,
-            ILogger<LianLianPayClient> logger)
+            ILogger<LianLianPayClient> logger,
+            IOptions<LianLianPayOptions> optionsAccessor)
         {
-            Options = optionsAccessor?.Value;
             Logger = logger;
+            Options = optionsAccessor?.Value;
 
             if (string.IsNullOrEmpty(Options.OidPartner))
             {
@@ -46,10 +46,6 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
             PublicKey = RSAUtilities.GetKeyParameterFormPublicKey(Options.RsaPublicKey);
         }
 
-        public LianLianPayNotifyClient(IOptions<LianLianPayOptions> optionsAccessor)
-            : this(optionsAccessor, null)
-        { }
-
         #endregion
 
         #region ILianLianPayNotifyClient Members
@@ -59,7 +55,7 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
             if (request.HasFormContentType)
             {
                 var parameters = await GetParametersAsync(request);
-                var query = HttpClientEx.BuildQuery(parameters);
+                var query = LianLianPayUtility.BuildQuery(parameters);
                 Logger?.LogTrace(0, "Request:{query}", query);
 
                 var parser = new LianLianPayDictionaryParser<T>();

+ 2 - 2
src/Essensoft.AspNetCore.Payment.LianLianPay/Parser/LianLianPayDictionaryParser.cs

@@ -1,6 +1,6 @@
-using Newtonsoft.Json;
-using System;
+using System;
 using System.Collections;
+using Newtonsoft.Json;
 
 namespace Essensoft.AspNetCore.Payment.LianLianPay.Parser
 {

+ 2 - 2
src/Essensoft.AspNetCore.Payment.LianLianPay/Parser/LianLianPayJsonParser.cs

@@ -1,6 +1,6 @@
-using Newtonsoft.Json;
-using System;
+using System;
 using System.Collections.Generic;
+using Newtonsoft.Json;
 
 namespace Essensoft.AspNetCore.Payment.LianLianPay.Parser
 {

+ 8 - 1
src/Essensoft.AspNetCore.Payment.LianLianPay/ServiceCollectionExtensions.cs

@@ -1,5 +1,6 @@
-using Essensoft.AspNetCore.Payment.LianLianPay;
 using System;
+using Essensoft.AspNetCore.Payment.LianLianPay;
+using Essensoft.AspNetCore.Payment.LianLianPay.Utility;
 
 namespace Microsoft.Extensions.DependencyInjection
 {
@@ -22,5 +23,11 @@ namespace Microsoft.Extensions.DependencyInjection
                 services.Configure(setupAction);
             }
         }
+
+        public static void AddLianLianPayHttpClient(
+            this IServiceCollection services)
+        {
+            services.AddHttpClient(LianLianPayUtility.DefaultClientName);
+        }
     }
 }

+ 0 - 55
src/Essensoft.AspNetCore.Payment.LianLianPay/Utility/HttpClientEx.cs

@@ -1,55 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Net;
-using System.Net.Http;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Essensoft.AspNetCore.Payment.LianLianPay.Utility
-{
-    /// <summary>
-    /// 网络工具类。
-    /// </summary>
-    public sealed class HttpClientEx : HttpClient
-    {
-        public HttpClientEx() : base()
-        {
-            Timeout = new TimeSpan(0, 0, 0, 10);
-            DefaultRequestHeaders.Connection.Add("keep-alive");
-        }
-
-        /// <summary>
-        /// 执行HTTP POST请求。
-        /// </summary>
-        /// <param name="url">请求地址</param>
-        /// <param name="content">请求内容</param>
-        /// <returns>HTTP响应</returns>
-        public async Task<string> DoPostAsync(string url, string content)
-        {
-            using (var requestContent = new StringContent(content, Encoding.UTF8, "application/json"))
-            using (var response = await PostAsync(url, requestContent))
-            using (var responseContent = response.Content)
-            {
-                return await responseContent.ReadAsStringAsync();
-            }
-        }
-
-        /// <summary>
-        /// 组装普通文本请求参数。
-        /// </summary>
-        /// <param name="parameters">Key-Value形式请求参数字典</param>
-        /// <returns>URL编码后的请求数据</returns>
-        public static string BuildQuery(IDictionary<string, string> parameters)
-        {
-            var content = new StringBuilder();
-            foreach (var iter in parameters)
-            {
-                if (!string.IsNullOrEmpty(iter.Value))
-                {
-                    content.Append(iter.Key + "=" + WebUtility.UrlEncode(iter.Value) + "&");
-                }
-            }
-            return content.ToString().Substring(0, content.Length - 1);
-        }
-    }
-}

+ 28 - 0
src/Essensoft.AspNetCore.Payment.LianLianPay/Utility/HttpClientUtility.cs

@@ -0,0 +1,28 @@
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Essensoft.AspNetCore.Payment.LianLianPay.Utility
+{
+    /// <summary>
+    /// 网络工具类。
+    /// </summary>
+    public static class HttpClientUtility
+    {
+        /// <summary>
+        /// 执行HTTP POST请求。
+        /// </summary>
+        /// <param name="url">请求地址</param>
+        /// <param name="content">请求内容</param>
+        /// <returns>HTTP响应</returns>
+        public static async Task<string> DoPostAsync(HttpClient client, string url, string content)
+        {
+            using (var requestContent = new StringContent(content, Encoding.UTF8, "application/json"))
+            using (var response = await client.PostAsync(url, requestContent))
+            using (var responseContent = response.Content)
+            {
+                return await responseContent.ReadAsStringAsync();
+            }
+        }
+    }
+}

+ 2 - 2
src/Essensoft.AspNetCore.Payment.LianLianPay/Utility/LianLianPaySecurity.cs

@@ -1,8 +1,8 @@
-using Essensoft.AspNetCore.Payment.Security;
-using Org.BouncyCastle.Crypto;
 using System;
 using System.Collections.Generic;
 using System.Text;
+using Essensoft.AspNetCore.Payment.Security;
+using Org.BouncyCastle.Crypto;
 
 namespace Essensoft.AspNetCore.Payment.LianLianPay.Utility
 {

+ 25 - 2
src/Essensoft.AspNetCore.Payment.LianLianPay/Utility/LianLianPayUtility.cs

@@ -1,11 +1,34 @@
-using Microsoft.AspNetCore.Http;
-using System;
+using System;
+using System.Collections.Generic;
+using System.Net;
 using System.Net.Http.Headers;
+using System.Text;
+using Microsoft.AspNetCore.Http;
 
 namespace Essensoft.AspNetCore.Payment.LianLianPay.Utility
 {
     public static class LianLianPayUtility
     {
+        public static readonly string DefaultClientName = "Payment.LianLianPay.Client";
+
+        /// <summary>
+        /// 组装普通文本请求参数。
+        /// </summary>
+        /// <param name="parameters">Key-Value形式请求参数字典</param>
+        /// <returns>URL编码后的请求数据</returns>
+        public static string BuildQuery(IDictionary<string, string> parameters)
+        {
+            var content = new StringBuilder();
+            foreach (var iter in parameters)
+            {
+                if (!string.IsNullOrEmpty(iter.Value))
+                {
+                    content.Append(iter.Key + "=" + WebUtility.UrlEncode(iter.Value) + "&");
+                }
+            }
+            return content.ToString().Substring(0, content.Length - 1);
+        }
+
         internal static bool HasTextJsonContentType(this HttpRequest request)
         {
             // Content-Type: text/json;charset=UTF-8

+ 2 - 1
src/Essensoft.AspNetCore.Payment.QPay/Essensoft.AspNetCore.Payment.QPay.csproj

@@ -18,7 +18,8 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.0" />
+    <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.2" />
+    <PackageReference Include="Microsoft.Extensions.Http" Version="2.1.1" />
   </ItemGroup>
 
   <ItemGroup>

+ 0 - 2
src/Essensoft.AspNetCore.Payment.QPay/IQPayClient.cs

@@ -4,8 +4,6 @@ namespace Essensoft.AspNetCore.Payment.QPay
 {
     public interface IQPayClient
     {
-        void SetTimeout(int timeout);
-
         /// <summary>
         /// 执行QPay API请求。
         /// </summary>

+ 2 - 2
src/Essensoft.AspNetCore.Payment.QPay/IQPayNotifyClient.cs

@@ -1,5 +1,5 @@
-using Microsoft.AspNetCore.Http;
-using System.Threading.Tasks;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
 
 namespace Essensoft.AspNetCore.Payment.QPay
 {

+ 32 - 54
src/Essensoft.AspNetCore.Payment.QPay/QPayClient.cs

@@ -1,12 +1,11 @@
-using Essensoft.AspNetCore.Payment.QPay.Parser;
+using System;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.QPay.Parser;
 using Essensoft.AspNetCore.Payment.QPay.Utility;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
-using System;
-using System.IO;
-using System.Net.Http;
-using System.Security.Cryptography.X509Certificates;
-using System.Threading.Tasks;
 
 namespace Essensoft.AspNetCore.Payment.QPay
 {
@@ -17,23 +16,22 @@ namespace Essensoft.AspNetCore.Payment.QPay
         private const string NONCE_STR = "nonce_str";
         private const string SIGN = "sign";
 
-        public QPayOptions Options { get; }
-
         public virtual ILogger Logger { get; set; }
 
-        protected internal HttpClientEx Client { get; set; }
+        public virtual IHttpClientFactory ClientFactory { get; set; }
 
-        protected internal HttpClientEx CertificateClient { get; set; }
+        public QPayOptions Options { get; }
 
         #region QPayClient Constructors
 
         public QPayClient(
-            IOptions<QPayOptions> optionsAccessor,
-            ILogger<QPayClient> logger)
+            ILogger<QPayClient> logger,
+            IHttpClientFactory clientFactory,
+            IOptions<QPayOptions> optionsAccessor)
         {
-            Options = optionsAccessor.Value;
             Logger = logger;
-            Client = new HttpClientEx();
+            ClientFactory = clientFactory;
+            Options = optionsAccessor.Value;
 
             if (string.IsNullOrEmpty(Options.MchId))
             {
@@ -44,38 +42,12 @@ namespace Essensoft.AspNetCore.Payment.QPay
             {
                 throw new ArgumentNullException(nameof(Options.Key));
             }
-
-            if (!string.IsNullOrEmpty(Options.Certificate))
-            {
-                var clientHandler = new HttpClientHandler();
-                clientHandler.ClientCertificates.Add(File.Exists(Options.Certificate) ? new X509Certificate2(Options.Certificate, Options.MchId) :
-                    new X509Certificate2(Convert.FromBase64String(Options.Certificate), Options.MchId, X509KeyStorageFlags.MachineKeySet));
-                CertificateClient = new HttpClientEx(clientHandler);
-            }
         }
 
-        public QPayClient(IOptions<QPayOptions> optionsAccessor)
-            : this(optionsAccessor, null)
-        { }
-
         #endregion
 
         #region IQPayClient Members
 
-        public void SetTimeout(int timeout)
-        {
-            Client.Timeout = new TimeSpan(0, 0, 0, timeout);
-
-            if (CertificateClient != null)
-            {
-                CertificateClient.Timeout = new TimeSpan(0, 0, 0, timeout);
-            }
-        }
-
-        #endregion IQPayClient Members
-
-        #region IQPayClient Members
-
         public async Task<T> ExecuteAsync<T>(IQPayRequest<T> request) where T : QPayResponse
         {
             // 字典排序
@@ -92,16 +64,19 @@ namespace Essensoft.AspNetCore.Payment.QPay
 
             sortedTxtParams.Add(SIGN, QPaySignature.SignWithKey(sortedTxtParams, Options.Key));
 
-            var content = HttpClientEx.BuildContent(sortedTxtParams);
+            var content = QPayUtility.BuildContent(sortedTxtParams);
             Logger?.LogTrace(0, "Request:{content}", content);
 
-            var body = await Client.DoPostAsync(request.GetRequestUrl(), content);
-            Logger?.LogTrace(1, "Response:{body}", body);
+            using (var client = ClientFactory.CreateClient(QPayUtility.DefaultClientName))
+            {
+                var body = await HttpClientUtility.DoPostAsync(client, request.GetRequestUrl(), content);
+                Logger?.LogTrace(1, "Response:{body}", body);
 
-            var parser = new QPayXmlParser<T>();
-            var rsp = parser.Parse(body);
-            CheckResponseSign(rsp);
-            return rsp;
+                var parser = new QPayXmlParser<T>();
+                var rsp = parser.Parse(body);
+                CheckResponseSign(rsp);
+                return rsp;
+            }
         }
 
         #endregion
@@ -123,16 +98,19 @@ namespace Essensoft.AspNetCore.Payment.QPay
             }
 
             sortedTxtParams.Add(SIGN, QPaySignature.SignWithKey(sortedTxtParams, Options.Key));
-            var content = HttpClientEx.BuildContent(sortedTxtParams);
+            var content = QPayUtility.BuildContent(sortedTxtParams);
             Logger?.LogTrace(0, "Request:{content}", content);
 
-            var body = await CertificateClient.DoPostAsync(request.GetRequestUrl(), content);
-            Logger?.LogTrace(1, "Response:{content}", body);
+            using (var client = ClientFactory.CreateClient(QPayUtility.CertificateClientName))
+            {
+                var body = await HttpClientUtility.DoPostAsync(client, request.GetRequestUrl(), content);
+                Logger?.LogTrace(1, "Response:{body}", body);
 
-            var parser = new QPayXmlParser<T>();
-            var rsp = parser.Parse(body);
-            CheckResponseSign(rsp);
-            return rsp;
+                var parser = new QPayXmlParser<T>();
+                var rsp = parser.Parse(body);
+                CheckResponseSign(rsp);
+                return rsp;
+            }
         }
 
         #endregion

+ 11 - 15
src/Essensoft.AspNetCore.Payment.QPay/QPayNotifyClient.cs

@@ -1,29 +1,29 @@
-using Essensoft.AspNetCore.Payment.QPay.Parser;
+using System;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.QPay.Parser;
 using Essensoft.AspNetCore.Payment.QPay.Utility;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
-using System;
-using System.IO;
-using System.Text;
-using System.Threading.Tasks;
 
 namespace Essensoft.AspNetCore.Payment.QPay
 {
     public class QPayNotifyClient : IQPayNotifyClient
     {
-        public QPayOptions Options { get; }
-
         public virtual ILogger Logger { get; set; }
 
-        #region QPayNotifyClient
+        public QPayOptions Options { get; }
+
+        #region QPayNotifyClient Constructors
 
         public QPayNotifyClient(
-            IOptions<QPayOptions> optionsAccessor,
-            ILogger<QPayNotifyClient> logger)
+            ILogger<QPayNotifyClient> logger,
+            IOptions<QPayOptions> optionsAccessor)
         {
-            Options = optionsAccessor.Value;
             Logger = logger;
+            Options = optionsAccessor.Value;
 
             if (string.IsNullOrEmpty(Options.Key))
             {
@@ -31,10 +31,6 @@ namespace Essensoft.AspNetCore.Payment.QPay
             }
         }
 
-        public QPayNotifyClient(IOptions<QPayOptions> optionsAccessor)
-            : this(optionsAccessor, null)
-        { }
-        
         #endregion
 
         #region IQPayNotifyClient Members

+ 0 - 5
src/Essensoft.AspNetCore.Payment.QPay/QPayOptions.cs

@@ -16,10 +16,5 @@
         /// API秘钥
         /// </summary>
         public string Key { get; set; }
-
-        /// <summary>
-        /// API证书文件 文件路径或文件的Base64串
-        /// </summary>
-        public string Certificate { get; set; }
     }
 }

+ 28 - 1
src/Essensoft.AspNetCore.Payment.QPay/ServiceCollectionExtensions.cs

@@ -1,5 +1,8 @@
-using Essensoft.AspNetCore.Payment.QPay;
 using System;
+using System.Net.Http;
+using System.Security.Cryptography.X509Certificates;
+using Essensoft.AspNetCore.Payment.QPay;
+using Essensoft.AspNetCore.Payment.QPay.Utility;
 
 namespace Microsoft.Extensions.DependencyInjection
 {
@@ -17,10 +20,34 @@ namespace Microsoft.Extensions.DependencyInjection
         {
             services.AddSingleton<QPayClient>();
             services.AddSingleton<QPayNotifyClient>();
+
             if (setupAction != null)
             {
                 services.Configure(setupAction);
             }
         }
+
+        public static void AddQPayHttpClient(
+            this IServiceCollection services)
+        {
+            services.AddQPayHttpClient(certificate: null);
+        }
+
+        public static void AddQPayHttpClient(
+            this IServiceCollection services,
+            X509Certificate2 certificate)
+        {
+            services.AddHttpClient(QPayUtility.DefaultClientName);
+
+            if (certificate != null)
+            {
+                services.AddHttpClient(QPayUtility.CertificateClientName).ConfigurePrimaryHttpMessageHandler(() =>
+                {
+                    var handler = new HttpClientHandler();
+                    handler.ClientCertificates.Add(certificate);
+                    return handler;
+                });
+            }
+        }
     }
 }

+ 0 - 57
src/Essensoft.AspNetCore.Payment.QPay/Utility/HttpClientEx.cs

@@ -1,57 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Net.Http;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Essensoft.AspNetCore.Payment.QPay.Utility
-{
-    public sealed class HttpClientEx : HttpClient
-    {
-        public HttpClientEx() : base()
-        {
-            Timeout = new TimeSpan(0, 0, 0, 10);
-            DefaultRequestHeaders.Connection.Add("keep-alive");
-        }
-
-        public HttpClientEx(HttpClientHandler handler) : base(handler)
-        {
-            Timeout = new TimeSpan(0, 0, 0, 10);
-            DefaultRequestHeaders.Connection.Add("keep-alive");
-        }
-
-        /// <summary>
-        /// 执行HTTP POST请求。
-        /// </summary>
-        /// <param name="url">请求地址</param>
-        /// <param name="content">请求参数</param>
-        /// <returns>HTTP响应</returns>
-        public async Task<string> DoPostAsync(string url, string content)
-        {
-            using (var requestContent = new StringContent(content, Encoding.UTF8, "application/xml"))
-            using (var response = await PostAsync(url, requestContent))
-            using (var responseContent = response.Content)
-            {
-                return await responseContent.ReadAsStringAsync();
-            }
-        }
-
-        /// <summary>
-        /// 组装普通文本请求参数。
-        /// </summary>
-        /// <param name="parameters">Key-Value形式请求参数字典</param>
-        /// <returns>URL编码后的请求数据</returns>
-        public static string BuildContent(IDictionary<string, string> parameters)
-        {
-            var content = new StringBuilder("<xml>");
-            foreach (var iter in parameters)
-            {
-                if (!string.IsNullOrEmpty(iter.Value))
-                {
-                    content.Append("<" + iter.Key + ">" + "<![CDATA[" + iter.Value + "]]></" + iter.Key + ">");
-                }
-            }
-            return content.Append("</xml>").ToString();
-        }
-    }
-}

+ 25 - 0
src/Essensoft.AspNetCore.Payment.QPay/Utility/HttpClientUtility.cs

@@ -0,0 +1,25 @@
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Essensoft.AspNetCore.Payment.QPay.Utility
+{
+    public static class HttpClientUtility
+    {
+        /// <summary>
+        /// 执行HTTP POST请求。
+        /// </summary>
+        /// <param name="url">请求地址</param>
+        /// <param name="content">请求参数</param>
+        /// <returns>HTTP响应</returns>
+        public static async Task<string> DoPostAsync(HttpClient client, string url, string content)
+        {
+            using (var requestContent = new StringContent(content, Encoding.UTF8, "application/xml"))
+            using (var response = await client.PostAsync(url, requestContent))
+            using (var responseContent = response.Content)
+            {
+                return await responseContent.ReadAsStringAsync();
+            }
+        }
+    }
+}

+ 2 - 2
src/Essensoft.AspNetCore.Payment.QPay/Utility/QPaySignature.cs

@@ -1,6 +1,6 @@
-using Essensoft.AspNetCore.Payment.Security;
-using System.Collections.Generic;
+using System.Collections.Generic;
 using System.Text;
+using Essensoft.AspNetCore.Payment.Security;
 
 namespace Essensoft.AspNetCore.Payment.QPay.Utility
 {

+ 29 - 0
src/Essensoft.AspNetCore.Payment.QPay/Utility/QPayUtility.cs

@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+using System.Text;
+
+namespace Essensoft.AspNetCore.Payment.QPay.Utility
+{
+    public static class QPayUtility
+    {
+        public static readonly string DefaultClientName = "Payment.QPay.Client";
+        public static readonly string CertificateClientName = "Payment.QPay.CertificateClient";
+
+        /// <summary>
+        /// 组装普通文本请求参数。
+        /// </summary>
+        /// <param name="parameters">Key-Value形式请求参数字典</param>
+        /// <returns>URL编码后的请求数据</returns>
+        public static string BuildContent(IDictionary<string, string> parameters)
+        {
+            var content = new StringBuilder("<xml>");
+            foreach (var iter in parameters)
+            {
+                if (!string.IsNullOrEmpty(iter.Value))
+                {
+                    content.Append("<" + iter.Key + ">" + "<![CDATA[" + iter.Value + "]]></" + iter.Key + ">");
+                }
+            }
+            return content.Append("</xml>").ToString();
+        }
+    }
+}

+ 3 - 3
src/Essensoft.AspNetCore.Payment.Security/AES_CTR_NoPadding.cs

@@ -1,7 +1,7 @@
-using Org.BouncyCastle.Crypto.Parameters;
-using Org.BouncyCastle.Security;
-using System;
+using System;
 using System.Text;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Security;
 
 namespace Essensoft.AspNetCore.Payment.Security
 {

+ 1 - 1
src/Essensoft.AspNetCore.Payment.Security/Essensoft.AspNetCore.Payment.Security.csproj

@@ -18,7 +18,7 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Portable.BouncyCastle" Version="1.8.2" />
+    <PackageReference Include="Portable.BouncyCastle" Version="1.8.3" />
   </ItemGroup>
 
 </Project>

+ 3 - 3
src/Essensoft.AspNetCore.Payment.Security/MD5WithRSA.cs

@@ -1,7 +1,7 @@
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Security;
-using System;
+using System;
 using System.Text;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
 
 namespace Essensoft.AspNetCore.Payment.Security
 {

+ 3 - 3
src/Essensoft.AspNetCore.Payment.Security/RSAUtilities.cs

@@ -1,11 +1,11 @@
-using Org.BouncyCastle.Asn1;
+using System;
+using System.Security.Cryptography;
+using Org.BouncyCastle.Asn1;
 using Org.BouncyCastle.Asn1.Pkcs;
 using Org.BouncyCastle.Asn1.X509;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Crypto.Parameters;
 using Org.BouncyCastle.Security;
-using System;
-using System.Security.Cryptography;
 
 namespace Essensoft.AspNetCore.Payment.Security
 {

+ 3 - 3
src/Essensoft.AspNetCore.Payment.Security/RSA_ECB_OAEPWithSHA1AndMGF1Padding.cs

@@ -1,7 +1,7 @@
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Security;
-using System;
+using System;
 using System.Text;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
 
 namespace Essensoft.AspNetCore.Payment.Security
 {

+ 3 - 3
src/Essensoft.AspNetCore.Payment.Security/RSA_ECB_PKCS1Padding.cs

@@ -1,7 +1,7 @@
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Security;
-using System;
+using System;
 using System.Text;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
 
 namespace Essensoft.AspNetCore.Payment.Security
 {

+ 3 - 3
src/Essensoft.AspNetCore.Payment.Security/RSA_NONE_PKCS1Padding.cs

@@ -1,7 +1,7 @@
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Security;
-using System;
+using System;
 using System.Text;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
 
 namespace Essensoft.AspNetCore.Payment.Security
 {

+ 3 - 3
src/Essensoft.AspNetCore.Payment.Security/SHA1WithRSA.cs

@@ -1,7 +1,7 @@
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Security;
-using System;
+using System;
 using System.Text;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
 
 namespace Essensoft.AspNetCore.Payment.Security
 {

+ 3 - 3
src/Essensoft.AspNetCore.Payment.Security/SHA256WithRSA.cs

@@ -1,7 +1,7 @@
-using Org.BouncyCastle.Crypto;
-using Org.BouncyCastle.Security;
-using System;
+using System;
 using System.Text;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Security;
 
 namespace Essensoft.AspNetCore.Payment.Security
 {

+ 3 - 3
src/Essensoft.AspNetCore.Payment.Security/SM3.cs

@@ -1,7 +1,7 @@
-using Org.BouncyCastle.Crypto.Digests;
-using Org.BouncyCastle.Security;
-using System;
+using System;
 using System.Text;
+using Org.BouncyCastle.Crypto.Digests;
+using Org.BouncyCastle.Security;
 
 namespace Essensoft.AspNetCore.Payment.Security
 {

+ 2 - 1
src/Essensoft.AspNetCore.Payment.UnionPay/Essensoft.AspNetCore.Payment.UnionPay.csproj

@@ -18,7 +18,8 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.0" />
+    <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.2" />
+    <PackageReference Include="Microsoft.Extensions.Http" Version="2.1.1" />
     <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
   </ItemGroup>
 

+ 0 - 2
src/Essensoft.AspNetCore.Payment.UnionPay/IUnionPayClient.cs

@@ -4,8 +4,6 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
 {
     public interface IUnionPayClient
     {
-        void SetTimeout(int timeout);
-
         /// <summary>
         /// 执行UnionPay API请求。
         /// </summary>

+ 2 - 2
src/Essensoft.AspNetCore.Payment.UnionPay/IUnionPayNotifyClient.cs

@@ -1,5 +1,5 @@
-using Microsoft.AspNetCore.Http;
-using System.Threading.Tasks;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
 
 namespace Essensoft.AspNetCore.Payment.UnionPay
 {

+ 1 - 1
src/Essensoft.AspNetCore.Payment.UnionPay/IUnionPayRequest.cs

@@ -27,7 +27,7 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
         /// </summary>
         /// <returns></returns>
         bool HasEncryptCertId();
-    
+
         /// <summary>
         /// 获取所有的Key-Value形式的文本请求参数字典。其中:
         /// Key: 请求参数名

+ 8 - 1
src/Essensoft.AspNetCore.Payment.UnionPay/ServiceCollectionExtensions.cs

@@ -1,5 +1,6 @@
-using Essensoft.AspNetCore.Payment.UnionPay;
 using System;
+using Essensoft.AspNetCore.Payment.UnionPay;
+using Essensoft.AspNetCore.Payment.UnionPay.Utility;
 
 namespace Microsoft.Extensions.DependencyInjection
 {
@@ -22,5 +23,11 @@ namespace Microsoft.Extensions.DependencyInjection
                 services.Configure(setupAction);
             }
         }
+
+        public static void AddUnionPayHttpClient(
+            this IServiceCollection services)
+        {
+            services.AddHttpClient(UnionPayUtility.DefaultClientName);
+        }
     }
 }

+ 36 - 44
src/Essensoft.AspNetCore.Payment.UnionPay/UnionPayClient.cs

@@ -1,12 +1,13 @@
-using Essensoft.AspNetCore.Payment.UnionPay.Parser;
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.UnionPay.Parser;
 using Essensoft.AspNetCore.Payment.UnionPay.Request;
 using Essensoft.AspNetCore.Payment.UnionPay.Utility;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Threading.Tasks;
 
 namespace Essensoft.AspNetCore.Payment.UnionPay
 {
@@ -24,21 +25,22 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
         private readonly UnionPayCertificate MiddleCertificate;
         private readonly UnionPayCertificate RootCertificate;
 
-        public UnionPayOptions Options { get; }
-
         public virtual ILogger Logger { get; set; }
 
-        protected internal HttpClientEx Client { get; set; }
+        public virtual IHttpClientFactory ClientFactory { get; set; }
+
+        public UnionPayOptions Options { get; }
 
         #region UnionPayClient Constructors
 
         public UnionPayClient(
-            IOptions<UnionPayOptions> optionsAccessor,
-            ILogger<UnionPayClient> logger)
+            ILogger<UnionPayClient> logger,
+            IHttpClientFactory clientFactory,
+            IOptions<UnionPayOptions> optionsAccessor)
         {
-            Options = optionsAccessor.Value;
             Logger = logger;
-            Client = new HttpClientEx();
+            ClientFactory = clientFactory;
+            Options = optionsAccessor.Value;
 
             if (string.IsNullOrEmpty(Options.SignCert))
             {
@@ -71,19 +73,6 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
             RootCertificate = UnionPaySignature.GetCertificate(Options.RootCert);
         }
 
-        public UnionPayClient(IOptions<UnionPayOptions> optionsAccessor)
-            : this(optionsAccessor, null)
-        { }
-
-        #endregion
-
-        #region IUnionPayClient Members
-
-        public void SetTimeout(int timeout)
-        {
-            Client.Timeout = new TimeSpan(0, 0, 0, timeout);
-        }
-
         #endregion
 
         #region IUnionPayClient Members
@@ -123,29 +112,32 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
 
             UnionPaySignature.Sign(txtParams, SignCertificate.certId, SignCertificate.key, Options.SecureKey);
 
-            var query = HttpClientEx.BuildQuery(txtParams);
+            var query = UnionPayUtility.BuildQuery(txtParams);
             Logger?.LogTrace(0, "Request:{query}", query);
 
-            var body = await Client.DoPostAsync(request.GetRequestUrl(Options.TestMode), query);
-            Logger?.LogTrace(1, "Response:{content}", body);
+            using (var client = ClientFactory.CreateClient(UnionPayUtility.DefaultClientName))
+            {
+                var body = await HttpClientUtility.DoPostAsync(client, request.GetRequestUrl(Options.TestMode), query);
+                Logger?.LogTrace(1, "Response:{content}", body);
 
-            var dic = ParseQueryString(body);
+                var dic = ParseQueryString(body);
 
-            if (string.IsNullOrEmpty(body))
-            {
-                throw new Exception("sign check fail: Body is Empty!");
-            }
+                if (string.IsNullOrEmpty(body))
+                {
+                    throw new Exception("sign check fail: Body is Empty!");
+                }
 
-            var ifValidateCNName = !Options.TestMode;
-            if (!UnionPaySignature.Validate(dic, RootCertificate.cert, MiddleCertificate.cert, Options.SecureKey, ifValidateCNName))
-            {
-                throw new Exception("sign check fail: check Sign and Data Fail!");
-            }
+                var ifValidateCNName = !Options.TestMode;
+                if (!UnionPaySignature.Validate(dic, RootCertificate.cert, MiddleCertificate.cert, Options.SecureKey, ifValidateCNName))
+                {
+                    throw new Exception("sign check fail: check Sign and Data Fail!");
+                }
 
-            var parser = new UnionPayDictionaryParser<T>();
-            var rsp = parser.Parse(dic);
-            rsp.Body = body;
-            return rsp;
+                var parser = new UnionPayDictionaryParser<T>();
+                var rsp = parser.Parse(dic);
+                rsp.Body = body;
+                return rsp;
+            }
         }
 
         #endregion
@@ -192,11 +184,11 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
                 {
                     if (tmpUrl.Contains("?"))
                     {
-                        tmpUrl = tmpUrl + "&" + HttpClientEx.BuildQuery(txtParams);
+                        tmpUrl = tmpUrl + "&" + UnionPayUtility.BuildQuery(txtParams);
                     }
                     else
                     {
-                        tmpUrl = tmpUrl + "?" + HttpClientEx.BuildQuery(txtParams);
+                        tmpUrl = tmpUrl + "?" + UnionPayUtility.BuildQuery(txtParams);
                     }
                 }
                 rsp.Body = tmpUrl;

+ 9 - 13
src/Essensoft.AspNetCore.Payment.UnionPay/UnionPayNotifyClient.cs

@@ -1,10 +1,10 @@
-using Essensoft.AspNetCore.Payment.UnionPay.Parser;
+using System;
+using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.UnionPay.Parser;
 using Essensoft.AspNetCore.Payment.UnionPay.Utility;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
-using System;
-using System.Threading.Tasks;
 
 namespace Essensoft.AspNetCore.Payment.UnionPay
 {
@@ -13,18 +13,18 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
         private readonly UnionPayCertificate MiddleCertificate;
         private readonly UnionPayCertificate RootCertificate;
 
-        public UnionPayOptions Options { get; }
-
         public virtual ILogger Logger { get; set; }
 
+        public UnionPayOptions Options { get; }
+
         #region UnionPayNotifyClient Constructors
 
         public UnionPayNotifyClient(
-            IOptions<UnionPayOptions> optionsAccessor,
-            ILogger<UnionPayNotifyClient> logger)
+            ILogger<UnionPayNotifyClient> logger,
+            IOptions<UnionPayOptions> optionsAccessor)
         {
-            Options = optionsAccessor.Value;
             Logger = logger;
+            Options = optionsAccessor.Value;
 
             if (string.IsNullOrEmpty(Options.MiddleCert))
             {
@@ -40,10 +40,6 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
             RootCertificate = UnionPaySignature.GetCertificate(Options.RootCert);
         }
 
-        public UnionPayNotifyClient(IOptions<UnionPayOptions> optionsAccessor)
-            : this(optionsAccessor, null)
-        { }
-
         #endregion
 
         #region IUnionPayNotifyClient Members
@@ -52,7 +48,7 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
         {
             var parameters = await GetParametersAsync(request);
 
-            var query = HttpClientEx.BuildQuery(parameters);
+            var query = UnionPayUtility.BuildQuery(parameters);
             Logger?.LogTrace(0, "Request:{query}", query);
 
             var parser = new UnionPayDictionaryParser<T>();

+ 0 - 82
src/Essensoft.AspNetCore.Payment.UnionPay/Utility/HttpClientEx.cs

@@ -1,82 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Net;
-using System.Net.Http;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Essensoft.AspNetCore.Payment.UnionPay.Utility
-{
-    /// <summary>
-    /// 网络工具类。
-    /// </summary>
-    public sealed class HttpClientEx : HttpClient
-    {
-        public HttpClientEx() : base()
-        {
-            Timeout = new TimeSpan(0, 0, 0, 10);
-            DefaultRequestHeaders.Connection.Add("keep-alive");
-        }
-
-        /// <summary>
-        /// 执行HTTP POST请求。
-        /// </summary>
-        /// <param name="url">请求地址</param>
-        /// <param name="content">请求参数</param>
-        /// <returns>HTTP响应</returns>
-        public async Task<string> DoPostAsync(string url, string content)
-        {
-            using (var requestContent = new StringContent(content, Encoding.UTF8, "application/x-www-form-urlencoded"))
-            using (var response = await PostAsync(url, requestContent))
-            using (var responseContent = response.Content)
-            {
-                return await responseContent.ReadAsStringAsync();
-            }
-        }
-
-        /// <summary>
-        /// 执行HTTP GET请求。
-        /// </summary>
-        /// <param name="url">请求地址</param>
-        /// <param name="parameters">请求参数</param>
-        /// <returns>HTTP响应</returns>
-        public async Task<string> DoGetAsync(string url, IDictionary<string, string> parameters)
-        {
-            if (parameters?.Count > 0)
-            {
-                if (url.Contains("?"))
-                {
-                    url = url + "&" + BuildQuery(parameters);
-                }
-                else
-                {
-                    url = url + "?" + BuildQuery(parameters);
-                }
-            }
-
-            using (var response = await GetAsync(url))
-            using (var responseContent = response.Content)
-            {
-                return await responseContent.ReadAsStringAsync();
-            }
-        }
-
-        /// <summary>
-        /// 组装普通文本请求参数。
-        /// </summary>
-        /// <param name="parameters">Key-Value形式请求参数字典</param>
-        /// <returns>URL编码后的请求数据</returns>
-        public static string BuildQuery(IDictionary<string, string> parameters)
-        {
-            var content = new StringBuilder();
-            foreach (var iter in parameters)
-            {
-                if (!string.IsNullOrEmpty(iter.Value))
-                {
-                    content.Append(iter.Key + "=" + WebUtility.UrlEncode(iter.Value) + "&");
-                }
-            }
-            return content.ToString().Substring(0, content.Length - 1);
-        }
-    }
-}

+ 56 - 0
src/Essensoft.AspNetCore.Payment.UnionPay/Utility/HttpClientUtility.cs

@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Essensoft.AspNetCore.Payment.UnionPay.Utility
+{
+    /// <summary>
+    /// 网络工具类。
+    /// </summary>
+    public static class HttpClientUtility
+    {
+        /// <summary>
+        /// 执行HTTP POST请求。
+        /// </summary>
+        /// <param name="url">请求地址</param>
+        /// <param name="content">请求参数</param>
+        /// <returns>HTTP响应</returns>
+        public static async Task<string> DoPostAsync(HttpClient client, string url, string content)
+        {
+            using (var requestContent = new StringContent(content, Encoding.UTF8, "application/x-www-form-urlencoded"))
+            using (var response = await client.PostAsync(url, requestContent))
+            using (var responseContent = response.Content)
+            {
+                return await responseContent.ReadAsStringAsync();
+            }
+        }
+
+        /// <summary>
+        /// 执行HTTP GET请求。
+        /// </summary>
+        /// <param name="url">请求地址</param>
+        /// <param name="parameters">请求参数</param>
+        /// <returns>HTTP响应</returns>
+        public static async Task<string> DoGetAsync(HttpClient client, string url, IDictionary<string, string> parameters)
+        {
+            if (parameters?.Count > 0)
+            {
+                if (url.Contains("?"))
+                {
+                    url = url + "&" + UnionPayUtility.BuildQuery(parameters);
+                }
+                else
+                {
+                    url = url + "?" + UnionPayUtility.BuildQuery(parameters);
+                }
+            }
+
+            using (var response = await client.GetAsync(url))
+            using (var responseContent = response.Content)
+            {
+                return await responseContent.ReadAsStringAsync();
+            }
+        }
+    }
+}

+ 7 - 7
src/Essensoft.AspNetCore.Payment.UnionPay/Utility/UnionPaySignature.cs

@@ -1,16 +1,16 @@
-using Essensoft.AspNetCore.Payment.Security;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Text;
+using Essensoft.AspNetCore.Payment.Security;
 using Org.BouncyCastle.Crypto;
 using Org.BouncyCastle.Pkcs;
 using Org.BouncyCastle.Pkix;
 using Org.BouncyCastle.Utilities.Collections;
 using Org.BouncyCastle.X509;
 using Org.BouncyCastle.X509.Store;
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.IO;
-using System.Net;
-using System.Text;
 
 namespace Essensoft.AspNetCore.Payment.UnionPay.Utility
 {

+ 25 - 2
src/Essensoft.AspNetCore.Payment.UnionPay/Utility/UnionPayUtils.cs → src/Essensoft.AspNetCore.Payment.UnionPay/Utility/UnionPayUtility.cs

@@ -1,11 +1,34 @@
 using System;
+using System.Collections.Generic;
 using System.ComponentModel;
 using System.Linq.Expressions;
+using System.Net;
+using System.Text;
 
 namespace Essensoft.AspNetCore.Payment.UnionPay.Utility
 {
-    public static class UnionPayUtils
+    public static class UnionPayUtility
     {
+        public static readonly string DefaultClientName = "Payment.UnionPay.Client";
+
+        /// <summary>
+        /// 组装普通文本请求参数。
+        /// </summary>
+        /// <param name="parameters">Key-Value形式请求参数字典</param>
+        /// <returns>URL编码后的请求数据</returns>
+        public static string BuildQuery(IDictionary<string, string> parameters)
+        {
+            var content = new StringBuilder();
+            foreach (var iter in parameters)
+            {
+                if (!string.IsNullOrEmpty(iter.Value))
+                {
+                    content.Append(iter.Key + "=" + WebUtility.UrlEncode(iter.Value) + "&");
+                }
+            }
+            return content.ToString().Substring(0, content.Length - 1);
+        }
+
         internal static object TryTo<T>(this object Object)
         {
             return Object.TryTo(typeof(T));
@@ -33,7 +56,7 @@ namespace Essensoft.AspNetCore.Payment.UnionPay.Utility
                     var ObjectValue = Object as string;
                     if (destinationType.IsEnum)
                     {
-                        return System.Enum.Parse(destinationType, ObjectValue, true);
+                        return Enum.Parse(destinationType, ObjectValue, true);
                     }
 
                     if (string.IsNullOrEmpty(ObjectValue))

+ 2 - 1
src/Essensoft.AspNetCore.Payment.WeChatPay/Essensoft.AspNetCore.Payment.WeChatPay.csproj

@@ -18,7 +18,8 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.0" />
+    <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.2" />
+    <PackageReference Include="Microsoft.Extensions.Http" Version="2.1.1" />
   </ItemGroup>
 
   <ItemGroup>

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

@@ -4,8 +4,6 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 {
     public interface IWeChatPayClient
     {
-        void SetTimeout(int timeout);
-
         /// <summary>
         /// 执行WeChatPay API请求。
         /// </summary>

+ 2 - 2
src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayNotifyClient.cs

@@ -1,5 +1,5 @@
-using Microsoft.AspNetCore.Http;
-using System.Threading.Tasks;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
 
 namespace Essensoft.AspNetCore.Payment.WeChatPay
 {

+ 28 - 1
src/Essensoft.AspNetCore.Payment.WeChatPay/ServiceCollectionExtensions.cs

@@ -1,5 +1,8 @@
-using Essensoft.AspNetCore.Payment.WeChatPay;
 using System;
+using System.Net.Http;
+using System.Security.Cryptography.X509Certificates;
+using Essensoft.AspNetCore.Payment.WeChatPay;
+using Essensoft.AspNetCore.Payment.WeChatPay.Utility;
 
 namespace Microsoft.Extensions.DependencyInjection
 {
@@ -17,10 +20,34 @@ namespace Microsoft.Extensions.DependencyInjection
         {
             services.AddSingleton<WeChatPayClient>();
             services.AddSingleton<WeChatPayNotifyClient>();
+
             if (setupAction != null)
             {
                 services.Configure(setupAction);
             }
         }
+
+        public static void AddWeChatPayHttpClient(
+            this IServiceCollection services)
+        {
+            services.AddWeChatPayHttpClient(certificate: null);
+        }
+
+        public static void AddWeChatPayHttpClient(
+            this IServiceCollection services,
+            X509Certificate2 certificate)
+        {
+            services.AddHttpClient(WeChatPayUtility.DefaultClientName);
+
+            if (certificate != null)
+            {
+                services.AddHttpClient(WeChatPayUtility.CertificateClientName).ConfigurePrimaryHttpMessageHandler(() =>
+                {
+                    var handler = new HttpClientHandler();
+                    handler.ClientCertificates.Add(certificate);
+                    return handler;
+                });
+            }
+        }
     }
 }

+ 0 - 57
src/Essensoft.AspNetCore.Payment.WeChatPay/Utility/HttpClientEx.cs

@@ -1,57 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Net.Http;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Essensoft.AspNetCore.Payment.WeChatPay.Utility
-{
-    public sealed class HttpClientEx : HttpClient
-    {
-        public HttpClientEx() : base()
-        {
-            Timeout = new TimeSpan(0, 0, 0, 10);
-            DefaultRequestHeaders.Connection.Add("keep-alive");
-        }
-
-        public HttpClientEx(HttpClientHandler handler) : base(handler)
-        {
-            Timeout = new TimeSpan(0, 0, 0, 10);
-            DefaultRequestHeaders.Connection.Add("keep-alive");
-        }
-
-        /// <summary>
-        /// 执行HTTP POST请求。
-        /// </summary>
-        /// <param name="url">请求地址</param>
-        /// <param name="content">请求参数</param>
-        /// <returns>HTTP响应</returns>
-        public async Task<string> DoPostAsync(string url, string content)
-        {
-            using (var requestContent = new StringContent(content, Encoding.UTF8, "application/xml"))
-            using (var response = await PostAsync(url, requestContent))
-            using (var responseContent = response.Content)
-            {
-                return await responseContent.ReadAsStringAsync();
-            }
-        }
-
-        /// <summary>
-        /// 组装普通文本请求参数。
-        /// </summary>
-        /// <param name="parameters">Key-Value形式请求参数字典</param>
-        /// <returns>URL编码后的请求数据</returns>
-        public static string BuildContent(IDictionary<string, string> parameters)
-        {
-            var content = new StringBuilder("<xml>");
-            foreach (var iter in parameters)
-            {
-                if (!string.IsNullOrEmpty(iter.Value))
-                {
-                    content.Append("<" + iter.Key + ">" + "<![CDATA[" + iter.Value + "]]></" + iter.Key + ">");
-                }
-            }
-            return content.Append("</xml>").ToString();
-        }
-    }
-}

+ 25 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Utility/HttpClientUtility.cs

@@ -0,0 +1,25 @@
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Utility
+{
+    public static class HttpClientUtility
+    {
+        /// <summary>
+        /// 执行HTTP POST请求。
+        /// </summary>
+        /// <param name="url">请求地址</param>
+        /// <param name="content">请求参数</param>
+        /// <returns>HTTP响应</returns>
+        public static async Task<string> DoPostAsync(HttpClient client, string url, string content)
+        {
+            using (var requestContent = new StringContent(content, Encoding.UTF8, "application/xml"))
+            using (var response = await client.PostAsync(url, requestContent))
+            using (var responseContent = response.Content)
+            {
+                return await responseContent.ReadAsStringAsync();
+            }
+        }
+    }
+}

+ 2 - 2
src/Essensoft.AspNetCore.Payment.WeChatPay/Utility/WeChatPaySignature.cs

@@ -1,6 +1,6 @@
-using Essensoft.AspNetCore.Payment.Security;
-using System.Collections.Generic;
+using System.Collections.Generic;
 using System.Text;
+using Essensoft.AspNetCore.Payment.Security;
 
 namespace Essensoft.AspNetCore.Payment.WeChatPay.Utility
 {

+ 25 - 2
src/Essensoft.AspNetCore.Payment.WeChatPay/Utility/WeChatPayUtility.cs

@@ -1,11 +1,34 @@
-using Microsoft.AspNetCore.Http;
-using System;
+using System;
+using System.Collections.Generic;
 using System.Net.Http.Headers;
+using System.Text;
+using Microsoft.AspNetCore.Http;
 
 namespace Essensoft.AspNetCore.Payment.WeChatPay.Utility
 {
     public static class WeChatPayUtility
     {
+        public static readonly string DefaultClientName = "Payment.WechatPay.Client";
+        public static readonly string CertificateClientName = "Payment.WechatPay.CertificateClient";
+
+        /// <summary>
+        /// 组装普通文本请求参数。
+        /// </summary>
+        /// <param name="parameters">Key-Value形式请求参数字典</param>
+        /// <returns>URL编码后的请求数据</returns>
+        public static string BuildContent(IDictionary<string, string> parameters)
+        {
+            var content = new StringBuilder("<xml>");
+            foreach (var iter in parameters)
+            {
+                if (!string.IsNullOrEmpty(iter.Value))
+                {
+                    content.Append("<" + iter.Key + ">" + "<![CDATA[" + iter.Value + "]]></" + iter.Key + ">");
+                }
+            }
+            return content.Append("</xml>").ToString();
+        }
+
         internal static bool HasTextJsonContentType(this HttpRequest request)
         {
             // Content-Type: text/json;charset=UTF-8

+ 36 - 63
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayClient.cs

@@ -1,15 +1,14 @@
-using Essensoft.AspNetCore.Payment.Security;
+using System;
+using System.Net.Http;
+using System.Text;
+using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.Security;
 using Essensoft.AspNetCore.Payment.WeChatPay.Parser;
 using Essensoft.AspNetCore.Payment.WeChatPay.Request;
 using Essensoft.AspNetCore.Payment.WeChatPay.Utility;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
 using Org.BouncyCastle.Crypto;
-using System;
-using System.IO;
-using System.Net.Http;
-using System.Security.Cryptography.X509Certificates;
-using System.Threading.Tasks;
 
 namespace Essensoft.AspNetCore.Payment.WeChatPay
 {
@@ -36,23 +35,22 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 
         private readonly AsymmetricKeyParameter PublicKey;
 
-        public WeChatPayOptions Options { get; set; }
-
         public virtual ILogger Logger { get; set; }
 
-        protected internal HttpClientEx Client { get; set; }
+        public virtual IHttpClientFactory ClientFactory { get; set; }
 
-        protected internal HttpClientEx CertificateClient { get; set; }
+        public WeChatPayOptions Options { get; }
 
         #region WeChatPayClient Constructors
 
         public WeChatPayClient(
-            IOptions<WeChatPayOptions> optionsAccessor,
-            ILogger<WeChatPayClient> logger)
+            ILogger<WeChatPayClient> logger,
+            IHttpClientFactory clientFactory,
+            IOptions<WeChatPayOptions> optionsAccessor)
         {
-            Options = optionsAccessor.Value;
             Logger = logger;
-            Client = new HttpClientEx();
+            ClientFactory = clientFactory;
+            Options = optionsAccessor.Value;
 
             if (string.IsNullOrEmpty(Options.AppId))
             {
@@ -69,39 +67,12 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
                 throw new ArgumentNullException(nameof(Options.Key));
             }
 
-            if (!string.IsNullOrEmpty(Options.Certificate))
-            {
-                var clientHandler = new HttpClientHandler();
-                clientHandler.ClientCertificates.Add(
-                    File.Exists(Options.Certificate) ? new X509Certificate2(Options.Certificate, Options.MchId) :
-                    new X509Certificate2(Convert.FromBase64String(Options.Certificate), Options.MchId, X509KeyStorageFlags.MachineKeySet));
-                CertificateClient = new HttpClientEx(clientHandler);
-            }
-
             if (!string.IsNullOrEmpty(Options.RsaPublicKey))
             {
                 PublicKey = RSAUtilities.GetPublicKeyParameterFormAsn1PublicKey(Options.RsaPublicKey);
             }
         }
 
-        public WeChatPayClient(IOptions<WeChatPayOptions> optionsAccessor)
-            : this(optionsAccessor, null)
-        { }
-
-        #endregion
-
-        #region IWeChatPayClient Members
-
-        public void SetTimeout(int timeout)
-        {
-            Client.Timeout = new TimeSpan(0, 0, 0, timeout);
-
-            if (CertificateClient != null)
-            {
-                CertificateClient.Timeout = new TimeSpan(0, 0, 0, timeout);
-            }
-        }
-
         #endregion
 
         #region IWeChatPayClient Members
@@ -121,16 +92,19 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
             }
 
             sortedTxtParams.Add(sign, WeChatPaySignature.SignWithKey(sortedTxtParams, Options.Key));
-            var content = HttpClientEx.BuildContent(sortedTxtParams);
+            var content = WeChatPayUtility.BuildContent(sortedTxtParams);
             Logger?.LogTrace(0, "Request:{content}", content);
 
-            var body = await Client.DoPostAsync(request.GetRequestUrl(), content);
-            Logger?.LogTrace(1, "Response:{body}", body);
+            using (var client = ClientFactory.CreateClient())
+            {
+                var body = await HttpClientUtility.DoPostAsync(client, request.GetRequestUrl(), content);
+                Logger?.LogTrace(1, "Response:{body}", body);
 
-            var parser = new WeChatPayXmlParser<T>();
-            var rsp = parser.Parse(body);
-            CheckResponseSign(rsp);
-            return rsp;
+                var parser = new WeChatPayXmlParser<T>();
+                var rsp = parser.Parse(body);
+                CheckResponseSign(rsp);
+                return rsp;
+            }
         }
 
         #endregion
@@ -142,11 +116,6 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
             var signType = true; // ture:MD5,false:HMAC-SHA256
             var excludeSignType = true;
 
-            if (CertificateClient == null)
-            {
-                throw new ArgumentNullException(nameof(Options.Certificate));
-            }
-
             // 字典排序
             var sortedTxtParams = new WeChatPayDictionary(request.GetParameters());
             if (request is WeChatPayTransfersRequest)
@@ -214,7 +183,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 
                 sortedTxtParams.Add(mch_id, Options.MchId);
             }
-            else if (request is WeChatPaySendRedPackRequest|| request is WeChatPaySendGroupRedPackRequest)
+            else if (request is WeChatPaySendRedPackRequest || request is WeChatPaySendGroupRedPackRequest)
             {
                 if (string.IsNullOrEmpty(sortedTxtParams.GetValue(wxappid)))
                 {
@@ -236,16 +205,20 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
             sortedTxtParams.Add(nonce_str, Guid.NewGuid().ToString("N"));
             sortedTxtParams.Add(sign, WeChatPaySignature.SignWithKey(sortedTxtParams, Options.Key, signType, excludeSignType));
 
-            var content = HttpClientEx.BuildContent(sortedTxtParams);
+            var content = WeChatPayUtility.BuildContent(sortedTxtParams);
             Logger?.LogTrace(0, "Request:{content}", content);
 
-            var body = await CertificateClient.DoPostAsync(request.GetRequestUrl(), content);
-            Logger?.LogTrace(1, "Response:{body}", body);
+            using (var client = ClientFactory.CreateClient(WeChatPayUtility.CertificateClientName))
+            {
+                var body = await HttpClientUtility.DoPostAsync(client, request.GetRequestUrl(), content);
 
-            var parser = new WeChatPayXmlParser<T>();
-            var rsp = parser.Parse(body);
-            CheckResponseSign(rsp, signType, excludeSignType);
-            return rsp;
+                Logger?.LogTrace(1, "Response:{body}", body);
+
+                var parser = new WeChatPayXmlParser<T>();
+                var rsp = parser.Parse(body);
+                CheckResponseSign(rsp, signType, excludeSignType);
+                return rsp;
+            }
         }
 
         #endregion
@@ -257,7 +230,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
             var sortedTxtParams = new WeChatPayDictionary(request.GetParameters());
             if (request is WeChatPayAppCallPaymentRequest)
             {
-                if(string.IsNullOrEmpty(sortedTxtParams.GetValue(appid)))
+                if (string.IsNullOrEmpty(sortedTxtParams.GetValue(appid)))
                 {
                     sortedTxtParams.Add(appid, Options.AppId);
                 }
@@ -270,7 +243,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
                 sortedTxtParams.Add(timestamp, WeChatPayUtility.GetTimeStamp());
                 sortedTxtParams.Add(sign, WeChatPaySignature.SignWithKey(sortedTxtParams, Options.Key));
             }
-            else if(request is WeChatPayLiteAppCallPaymentRequest || request is WeChatPayH5CallPaymentRequest)
+            else if (request is WeChatPayLiteAppCallPaymentRequest || request is WeChatPayH5CallPaymentRequest)
             {
                 if (string.IsNullOrEmpty(sortedTxtParams.GetValue(appId)))
                 {

+ 10 - 14
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayNotifyClient.cs

@@ -1,31 +1,31 @@
-using Essensoft.AspNetCore.Payment.Security;
+using System;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.Security;
 using Essensoft.AspNetCore.Payment.WeChatPay.Notify;
 using Essensoft.AspNetCore.Payment.WeChatPay.Parser;
 using Essensoft.AspNetCore.Payment.WeChatPay.Utility;
 using Microsoft.AspNetCore.Http;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
-using System;
-using System.IO;
-using System.Text;
-using System.Threading.Tasks;
 
 namespace Essensoft.AspNetCore.Payment.WeChatPay
 {
     public class WeChatPayNotifyClient : IWeChatPayNotifyClient
     {
-        public WeChatPayOptions Options { get; set; }
-
         public virtual ILogger Logger { get; set; }
 
+        public WeChatPayOptions Options { get; }
+
         #region WeChatPayNotifyClient Constructors
 
         public WeChatPayNotifyClient(
-            IOptions<WeChatPayOptions> optionsAccessor,
-            ILogger<WeChatPayNotifyClient> logger)
+            ILogger<WeChatPayNotifyClient> logger,
+            IOptions<WeChatPayOptions> optionsAccessor)
         {
-            Options = optionsAccessor.Value;
             Logger = logger;
+            Options = optionsAccessor.Value;
 
             if (string.IsNullOrEmpty(Options.Key))
             {
@@ -33,10 +33,6 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
             }
         }
 
-        public WeChatPayNotifyClient(IOptions<WeChatPayOptions> optionsAccessor)
-            : this(optionsAccessor, null)
-        { }
-
         #endregion
 
         #region IWeChatPayNotifyClient Members

+ 0 - 5
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayOptions.cs

@@ -17,11 +17,6 @@
         /// </summary>
         public string Key { get; set; }
 
-        /// <summary>
-        /// API证书文件 文件路径或文件的Base64串
-        /// </summary>
-        public string Certificate { get; set; }
-
         /// <summary>
         /// RSA公钥 企业付款到银行卡
         /// </summary>