Browse Source

Merge pull request #10 from Essensoft/dev-2.x

Dev 2.x
Roc 7 years ago
parent
commit
a37ba43c2a
75 changed files with 822 additions and 767 deletions
  1. 2 2
      README.MD
  2. 41 10
      docs/Configuration.md
  3. 23 21
      docs/Using-HttpClient.md
  4. 5 5
      samples/WebApplicationSample/Controllers/AlipayController.cs
  5. 3 3
      samples/WebApplicationSample/Controllers/JDPayController.cs
  6. 3 3
      samples/WebApplicationSample/Controllers/LianLianPayController.cs
  7. 12 12
      samples/WebApplicationSample/Controllers/NotifyController.cs
  8. 6 6
      samples/WebApplicationSample/Controllers/QPayController.cs
  9. 3 3
      samples/WebApplicationSample/Controllers/UnionPayController.cs
  10. 13 14
      samples/WebApplicationSample/Controllers/WeChatPayController.cs
  11. 25 20
      samples/WebApplicationSample/Startup.cs
  12. 86 92
      src/Essensoft.AspNetCore.Payment.Alipay/AlipayClient.cs
  13. 10 9
      src/Essensoft.AspNetCore.Payment.Alipay/AlipayNotifyClient.cs
  14. 0 2
      src/Essensoft.AspNetCore.Payment.Alipay/AlipayOptions.cs
  15. 0 14
      src/Essensoft.AspNetCore.Payment.Alipay/AlipayOptionsAccessor.cs
  16. 3 3
      src/Essensoft.AspNetCore.Payment.Alipay/Essensoft.AspNetCore.Payment.Alipay.csproj
  17. 41 2
      src/Essensoft.AspNetCore.Payment.Alipay/IAlipayClient.cs
  18. 16 1
      src/Essensoft.AspNetCore.Payment.Alipay/IAlipayNotifyClient.cs
  19. 1 1
      src/Essensoft.AspNetCore.Payment.Alipay/Request/AlipayFlashsalesStockSyncUpdateRequest.cs
  20. 1 1
      src/Essensoft.AspNetCore.Payment.Alipay/Request/AlipayMicropayOrderFreezeRequest.cs
  21. 3 3
      src/Essensoft.AspNetCore.Payment.Alipay/Request/AlipayMobileCodeCreateRequest.cs
  22. 1 1
      src/Essensoft.AspNetCore.Payment.Alipay/Request/AlipayOpenAgentFacetofaceSignRequest.cs
  23. 1 1
      src/Essensoft.AspNetCore.Payment.Alipay/Request/AlipayOpenAgentMobilepaySignRequest.cs
  24. 1 1
      src/Essensoft.AspNetCore.Payment.Alipay/Request/AlipayOpenAgentZhimabriefSignRequest.cs
  25. 2 2
      src/Essensoft.AspNetCore.Payment.Alipay/Request/AlipayPointOrderAddRequest.cs
  26. 1 1
      src/Essensoft.AspNetCore.Payment.Alipay/Request/AlipayZdatafrontCommonQueryRequest.cs
  27. 1 1
      src/Essensoft.AspNetCore.Payment.Alipay/Request/AlipayZdatafrontDatatransferedFileuploadRequest.cs
  28. 2 8
      src/Essensoft.AspNetCore.Payment.Alipay/ServiceCollectionExtensions.cs
  29. 3 3
      src/Essensoft.AspNetCore.Payment.JDPay/Essensoft.AspNetCore.Payment.JDPay.csproj
  30. 28 0
      src/Essensoft.AspNetCore.Payment.JDPay/IJDPayClient.cs
  31. 18 0
      src/Essensoft.AspNetCore.Payment.JDPay/IJDPayNotifyClient.cs
  32. 41 43
      src/Essensoft.AspNetCore.Payment.JDPay/JDPayClient.cs
  33. 21 35
      src/Essensoft.AspNetCore.Payment.JDPay/JDPayNotifyClient.cs
  34. 0 2
      src/Essensoft.AspNetCore.Payment.JDPay/JDPayOptions.cs
  35. 0 14
      src/Essensoft.AspNetCore.Payment.JDPay/JDPayOptionsAccessor.cs
  36. 2 9
      src/Essensoft.AspNetCore.Payment.JDPay/ServiceCollectionExtensions.cs
  37. 3 3
      src/Essensoft.AspNetCore.Payment.LianLianPay/Essensoft.AspNetCore.Payment.LianLianPay.csproj
  38. 13 0
      src/Essensoft.AspNetCore.Payment.LianLianPay/ILianLianPayClient.cs
  39. 18 0
      src/Essensoft.AspNetCore.Payment.LianLianPay/ILianLianPayNotifyClient.cs
  40. 20 34
      src/Essensoft.AspNetCore.Payment.LianLianPay/LianLianPayClient.cs
  41. 17 26
      src/Essensoft.AspNetCore.Payment.LianLianPay/LianLianPayNotifyClient.cs
  42. 0 2
      src/Essensoft.AspNetCore.Payment.LianLianPay/LianLianPayOptions.cs
  43. 0 14
      src/Essensoft.AspNetCore.Payment.LianLianPay/LianLianPayOptionsAccessor.cs
  44. 2 9
      src/Essensoft.AspNetCore.Payment.LianLianPay/ServiceCollectionExtensions.cs
  45. 3 3
      src/Essensoft.AspNetCore.Payment.QPay/Essensoft.AspNetCore.Payment.QPay.csproj
  46. 27 3
      src/Essensoft.AspNetCore.Payment.QPay/IQPayClient.cs
  47. 18 0
      src/Essensoft.AspNetCore.Payment.QPay/IQPayNotifyClient.cs
  48. 28 27
      src/Essensoft.AspNetCore.Payment.QPay/QPayClient.cs
  49. 12 11
      src/Essensoft.AspNetCore.Payment.QPay/QPayNotifyClient.cs
  50. 0 3
      src/Essensoft.AspNetCore.Payment.QPay/QPayOptions.cs
  51. 0 14
      src/Essensoft.AspNetCore.Payment.QPay/QPayOptionsAccessor.cs
  52. 2 29
      src/Essensoft.AspNetCore.Payment.QPay/ServiceCollectionExtensions.cs
  53. 3 3
      src/Essensoft.AspNetCore.Payment.Security/Essensoft.AspNetCore.Payment.Security.csproj
  54. 3 3
      src/Essensoft.AspNetCore.Payment.UnionPay/Essensoft.AspNetCore.Payment.UnionPay.csproj
  55. 26 0
      src/Essensoft.AspNetCore.Payment.UnionPay/IUnionPayClient.cs
  56. 18 0
      src/Essensoft.AspNetCore.Payment.UnionPay/IUnionPayNotifyClient.cs
  57. 2 9
      src/Essensoft.AspNetCore.Payment.UnionPay/ServiceCollectionExtensions.cs
  58. 34 66
      src/Essensoft.AspNetCore.Payment.UnionPay/UnionPayClient.cs
  59. 13 17
      src/Essensoft.AspNetCore.Payment.UnionPay/UnionPayNotifyClient.cs
  60. 0 2
      src/Essensoft.AspNetCore.Payment.UnionPay/UnionPayOptions.cs
  61. 0 14
      src/Essensoft.AspNetCore.Payment.UnionPay/UnionPayOptionsAccessor.cs
  62. 3 3
      src/Essensoft.AspNetCore.Payment.WeChatPay/Essensoft.AspNetCore.Payment.WeChatPay.csproj
  63. 4 1
      src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayCallRequest.cs
  64. 4 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayCertificateRequest.cs
  65. 33 6
      src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayClient.cs
  66. 18 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayNotifyClient.cs
  67. 4 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayRequest.cs
  68. 1 1
      src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayAppCallPaymentRequest.cs
  69. 1 1
      src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayH5CallPaymentRequest.cs
  70. 1 1
      src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayLiteAppCallPaymentRequest.cs
  71. 2 31
      src/Essensoft.AspNetCore.Payment.WeChatPay/ServiceCollectionExtensions.cs
  72. 57 54
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayClient.cs
  73. 13 12
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayNotifyClient.cs
  74. 0 3
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayOptions.cs
  75. 0 14
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayOptionsAccessor.cs

+ 2 - 2
README.MD

@@ -64,9 +64,9 @@ Essensoft.AspNetCore.Payment.Security       | [![NuGet](https://img.shields.io/n
 
 ## 使用方式
 
-* [配置参数](docs/Configuration.md)
+* [配置参数(多商户)](docs/Configuration.md)
 
-* [使用/配置 HttpClient](docs/Using-HttpClient.md)
+* [配置HttpClient(API证书)](docs/Using-HttpClient.md)
 
 * [参考示例项目](samples/WebApplicationSample)
 

+ 41 - 10
docs/Configuration.md

@@ -1,31 +1,62 @@
-# 关于Payment中 参数置 介绍
+# 关于Payment中 参数置 介绍
 
-* 以WeChatPay为例,有以下2种方式:
+## 以WeChatPay为例,有以下3种方式:
 
-1. 引入WeChatPay同时配置参数
+1. 引入WeChatPay同时设置参数(内置选项配置)
 ```
 services.AddWeChatPay(opt => { opt.AppId = "xx"; opt.MchId = "xx"; opt.Key = "xx"; });
-services.AddWeChatPayHttpClient();
 ```
 
-2. 引入WeChatPay
+2. 使用配置文件的方式设置参数(选项配置) [微软官方教程](https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.1)
+
+* 设置配置文件 appsettings.json
 ```
-services.AddWeChatPay();
-services.AddWeChatPayHttpClient();
+{
+  "WeChatPay": {
+    "AppId": "xxx",
+    "MchId": "xxx",
+    "Key": "xxx",
+    "Certificate": "xxx"
+  },
+}
 ```
-* 使用配置文件配置参数 [微软官方教程](https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.1)
+
+* 配置选项
 ```
 services.Configure<WeChatPayOptions>(Configuration.GetSection("WeChatPay"));
 ```
+
+3. 多商户模式配置
+
 * 设置配置文件 appsettings.json
 ```
 {
-  "WeChatPay": {
+  "WeChatPay1": {
     "AppId": "xxx",
     "MchId": "xxx",
     "Key": "xxx",
     "Certificate": "xxx"
   },
+  "WeChatPay2": {
+  "AppId": "xxx",
+  "MchId": "xxx",
+  "Key": "xxx",
+  "Certificate": "xxx"
+  },
 }
 ```
-注:配置参数可查看 XXXXOptions,例如 WeChatPayOptions、AlipayOptions
+
+* 配置多个商户选项
+
+```
+// WeChatPay1 / WeChatPay2 为选项名称 可自定义 调用的时候需要用到。
+services.Configure<WeChatPayOptions>("WeChatPay1",Configuration.GetSection("WeChatPay1"));
+services.Configure<WeChatPayOptions>("WeChatPay2",Configuration.GetSection("WeChatPay2"));
+```
+
+* 调用指定商户配置选项(根据选项名称)
+```
+await _client.ExecuteAsync(request, "WeChatPay1");
+```
+
+注:具体配置选项参数可查看对应Options,例如 WeChatPayOptions、AlipayOptions、...

+ 23 - 21
docs/Using-HttpClient.md

@@ -1,33 +1,35 @@
 # 关于Payment中使用 HttpClient 配置介绍
 
-* 以WeChatPay为例,有以下几种方式:
+## Payment使用HttpClient提交API请求,务必在项目中引入它。
+
+## 引入 HttpClient
 
-1. 添加默认HttpClient供WeChatPayClient使用. (未使用到API证书时适用)
 ```
-services.AddWeChatPayHttpClient();
+services.AddHttpClient();
 ```
 
-2. 添加默认HttpClient和证书HttpClient供WeChatPayClient使用 (目前仅WeChatPay\QPay部分API有使用到API证书配置) 
+## API证书配置(仅QPay 与 WeChatPay的部分API 使用到API证书。)
 
-* 根据证书路径
-```
-services.AddWeChatPayHttpClient();
-services.AddWeChatPayCertificateHttpClient(new X509Certificate2(Configuration["WeChatPay:Certificate"], Configuration["WeChatPay:MchId"], X509KeyStorageFlags.MachineKeySet));
-//services.AddWeChatPayCertificateHttpClient("certificateName1", new X509Certificate2(Configuration["WeChatPay:Certificate"], Configuration["WeChatPay:MchId"], X509KeyStorageFlags.MachineKeySet));
-//若配置证书名为"certificateName1", 则执行请求时需要配置证书名(该API请求需要证书的情况下), 如: await _client.ExecuteAsync(request, "certificateName1");
-```
+* API证书配置
 
-* 根据证书Base64String
 ```
-services.AddWeChatPayHttpClient();
-services.AddWeChatPayCertificateHttpClient(new X509Certificate2(Convert.FromBase64String(Configuration["WeChatPay:Certificate"]), Configuration["WeChatPay:MchId"], X509KeyStorageFlags.MachineKeySet));
-//services.AddWeChatPayCertificateHttpClient("certificateName1", new X509Certificate2(Convert.FromBase64String(Configuration["WeChatPay:Certificate"]), Configuration["WeChatPay:MchId"], X509KeyStorageFlags.MachineKeySet));
-//若配置证书名为"certificateName1", 则执行请求时需要配置证书名(该API请求需要证书的情况下), 如: await _client.ExecuteAsync(request, "certificateName1");
+// 证书名称可自定义
+services.AddHttpClient("证书名称").ConfigurePrimaryHttpMessageHandler(() =>
+{
+    // 载入证书
+    var certificate = new X509Certificate2("", "", X509KeyStorageFlags.MachineKeySet);
+    var handler = new HttpClientHandler();
+    handler.ClientCertificates.Add(certificate);
+    return handler;
+});
 ```
 
-3.  自行配置HttpClient供WeChatPayClient使用 (目前仅WeChatPay\QPay部分API有使用到API证书配置) 
+* API证书使用
+
+```
+var request = new WeChatPayRefundRequest()
+{
+...
+};
+var response = await _client.ExecuteAsync(request, "证书名称");
 ```
-services.AddHttpClient(WeChatPayOptions.DefaultClientName);
-services.AddHttpClient(WeChatPayOptions.CertificateClientName + "." + "Default").ConfigurePrimaryHttpMessageHandler(() => { ... });
-// "Default" 为默认执行请求的API证书名, 可自行配置.
-```

+ 5 - 5
samples/WebApplicationSample/Controllers/AlipayController.cs

@@ -1,19 +1,19 @@
-using Essensoft.AspNetCore.Payment.Alipay;
+using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.Alipay;
 using Essensoft.AspNetCore.Payment.Alipay.Domain;
 using Essensoft.AspNetCore.Payment.Alipay.Notify;
 using Essensoft.AspNetCore.Payment.Alipay.Request;
 using Microsoft.AspNetCore.Mvc;
 using WebApplicationSample.Models;
-using System.Threading.Tasks;
 
 namespace WebApplicationSample.Controllers
 {
     public class AlipayController : Controller
     {
-        private readonly AlipayClient _client = null;
-        private readonly AlipayNotifyClient _notifyClient = null;
+        private readonly IAlipayClient _client = null;
+        private readonly IAlipayNotifyClient _notifyClient = null;
 
-        public AlipayController(AlipayClient client, AlipayNotifyClient notifyClient)
+        public AlipayController(IAlipayClient client, IAlipayNotifyClient notifyClient)
         {
             _client = client;
             _notifyClient = notifyClient;

+ 3 - 3
samples/WebApplicationSample/Controllers/JDPayController.cs

@@ -9,10 +9,10 @@ namespace WebApplicationSample.Controllers
 {
     public class JDPayController : Controller
     {
-        private readonly JDPayClient _client = null;
-        private readonly JDPayNotifyClient _notifyClient = null;
+        private readonly IJDPayClient _client = null;
+        private readonly IJDPayNotifyClient _notifyClient = null;
 
-        public JDPayController(JDPayClient client, JDPayNotifyClient notifyClient)
+        public JDPayController(IJDPayClient client, IJDPayNotifyClient notifyClient)
         {
             _client = client;
             _notifyClient = notifyClient;

+ 3 - 3
samples/WebApplicationSample/Controllers/LianLianPayController.cs

@@ -9,10 +9,10 @@ namespace WebApplicationSample.Controllers
 {
     public class LianLianPayController : Controller
     {
-        private readonly LianLianPayClient _client = null;
-        private readonly LianLianPayNotifyClient _notifyClient = null;
+        private readonly ILianLianPayClient _client = null;
+        private readonly ILianLianPayNotifyClient _notifyClient = null;
 
-        public LianLianPayController(LianLianPayClient client, LianLianPayNotifyClient notifyClient)
+        public LianLianPayController(ILianLianPayClient client, ILianLianPayNotifyClient notifyClient)
         {
             _client = client;
             _notifyClient = notifyClient;

+ 12 - 12
samples/WebApplicationSample/Controllers/NotifyController.cs

@@ -21,8 +21,8 @@ namespace WebApplicationSample.Controllers
     [Route("notify/alipay")]
     public class AlipayNotifyController : Controller
     {
-        private readonly AlipayNotifyClient _client = null;
-        public AlipayNotifyController(AlipayNotifyClient client)
+        private readonly IAlipayNotifyClient _client = null;
+        public AlipayNotifyController(IAlipayNotifyClient client)
         {
             _client = client;
         }
@@ -135,8 +135,8 @@ namespace WebApplicationSample.Controllers
     [Route("notify/wechatpay")]
     public class WeChatPayNotifyController : Controller
     {
-        private readonly WeChatPayNotifyClient _client = null;
-        public WeChatPayNotifyController(WeChatPayNotifyClient client)
+        private readonly IWeChatPayNotifyClient _client = null;
+        public WeChatPayNotifyController(IWeChatPayNotifyClient client)
         {
             _client = client;
         }
@@ -204,8 +204,8 @@ namespace WebApplicationSample.Controllers
     [Route("notify/qpay")]
     public class QPayNotifyController : Controller
     {
-        private readonly QPayNotifyClient _client = null;
-        public QPayNotifyController(QPayNotifyClient client)
+        private readonly IQPayNotifyClient _client = null;
+        public QPayNotifyController(IQPayNotifyClient client)
         {
             _client = client;
         }
@@ -266,8 +266,8 @@ namespace WebApplicationSample.Controllers
     [Route("notify/jdpay")]
     public class JDPayNotifyController : Controller
     {
-        private readonly JDPayNotifyClient _client = null;
-        public JDPayNotifyController(JDPayNotifyClient client)
+        private readonly IJDPayNotifyClient _client = null;
+        public JDPayNotifyController(IJDPayNotifyClient client)
         {
             _client = client;
         }
@@ -312,8 +312,8 @@ namespace WebApplicationSample.Controllers
     [Route("notify/lianlianpay")]
     public class LianLianPayNotifyController : Controller
     {
-        private readonly LianLianPayNotifyClient _client = null;
-        public LianLianPayNotifyController(LianLianPayNotifyClient client)
+        private readonly ILianLianPayNotifyClient _client = null;
+        public LianLianPayNotifyController(ILianLianPayNotifyClient client)
         {
             _client = client;
         }
@@ -358,8 +358,8 @@ namespace WebApplicationSample.Controllers
     [Route("notify/unionpay")]
     public class UnionPayNotifyController : Controller
     {
-        private readonly UnionPayNotifyClient _client = null;
-        public UnionPayNotifyController(UnionPayNotifyClient client)
+        private readonly IUnionPayNotifyClient _client = null;
+        public UnionPayNotifyController(IUnionPayNotifyClient client)
         {
             _client = client;
         }

+ 6 - 6
samples/WebApplicationSample/Controllers/QPayController.cs

@@ -1,15 +1,15 @@
-using Essensoft.AspNetCore.Payment.QPay;
+using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.QPay;
 using Essensoft.AspNetCore.Payment.QPay.Request;
 using Microsoft.AspNetCore.Mvc;
 using WebApplicationSample.Models;
-using System.Threading.Tasks;
 
 namespace WebApplicationSample.Controllers
 {
     public class QPayController : Controller
     {
-        private readonly QPayClient _client = null;
-        public QPayController(QPayClient client)
+        private readonly IQPayClient _client = null;
+        public QPayController(IQPayClient client)
         {
             _client = client;
         }
@@ -150,7 +150,7 @@ namespace WebApplicationSample.Controllers
             {
                 OutTradeNo = viewModel.OutTradeNo,
             };
-            var response = await _client.ExecuteAsync(request);
+            var response = await _client.ExecuteAsync(request, "qpayCertificateName");
             ViewData["response"] = response.Body;
             return View();
         }
@@ -192,7 +192,7 @@ namespace WebApplicationSample.Controllers
                 OpUserId = viewModel.OpUserId,
                 OpUserPasswd = viewModel.OpUserPasswd,
             };
-            var response = await _client.ExecuteAsync(request);
+            var response = await _client.ExecuteAsync(request, "qpayCertificateName");
             ViewData["response"] = response.Body;
             return View();
         }

+ 3 - 3
samples/WebApplicationSample/Controllers/UnionPayController.cs

@@ -9,10 +9,10 @@ namespace WebApplicationSample.Controllers
 {
     public class UnionPayController : Controller
     {
-        private readonly UnionPayClient _client = null;
-        private readonly UnionPayNotifyClient _notifyClient = null;
+        private readonly IUnionPayClient _client = null;
+        private readonly IUnionPayNotifyClient _notifyClient = null;
 
-        public UnionPayController(UnionPayClient client, UnionPayNotifyClient notifyClient)
+        public UnionPayController(IUnionPayClient client, IUnionPayNotifyClient notifyClient)
         {
             _client = client;
             _notifyClient = notifyClient;

+ 13 - 14
samples/WebApplicationSample/Controllers/WeChatPayController.cs

@@ -1,17 +1,16 @@
-using Essensoft.AspNetCore.Payment.WeChatPay;
+using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.WeChatPay;
 using Essensoft.AspNetCore.Payment.WeChatPay.Request;
 using Microsoft.AspNetCore.Mvc;
 using Newtonsoft.Json;
 using WebApplicationSample.Models;
-using System.Threading.Tasks;
 
 namespace WebApplicationSample.Controllers
 {
     public class WeChatPayController : Controller
     {
-        private readonly WeChatPayClient _client = null;
-
-        public WeChatPayController(WeChatPayClient client)
+        private readonly IWeChatPayClient _client = null;
+        public WeChatPayController(IWeChatPayClient client)
         {
             _client = client;
         }
@@ -159,7 +158,7 @@ namespace WebApplicationSample.Controllers
                 TradeType = viewModel.TradeType,
             };
             var response = await _client.ExecuteAsync(request);
-            
+
             // mweb_url为拉起微信支付收银台的中间页面,可通过访问该url来拉起微信客户端,完成支付,mweb_url的有效期为5分钟。
             return Redirect(response.MwebUrl);
         }
@@ -234,7 +233,7 @@ namespace WebApplicationSample.Controllers
                 TransactionId = viewModel.TransactionId,
                 OutTradeNo = viewModel.OutTradeNo,
             };
-            var response = await _client.ExecuteAsync(request);
+            var response = await _client.ExecuteAsync(request, "wechatpayCertificateName");
             ViewData["response"] = response.Body;
             return View();
         }
@@ -276,7 +275,7 @@ namespace WebApplicationSample.Controllers
                 RefundDesc = viewModel.RefundDesc,
                 NotifyUrl = viewModel.NotifyUrl,
             };
-            var response = await _client.ExecuteAsync(request);
+            var response = await _client.ExecuteAsync(request, "wechatpayCertificateName");
             ViewData["response"] = response.Body;
             return View();
         }
@@ -337,7 +336,7 @@ namespace WebApplicationSample.Controllers
                 AccountType = viewModel.AccountType,
                 TarType = viewModel.TarType,
             };
-            var response = await _client.ExecuteAsync(request);
+            var response = await _client.ExecuteAsync(request, "wechatpayCertificateName");
             ViewData["response"] = response.Body;
             return View();
         }
@@ -361,7 +360,7 @@ namespace WebApplicationSample.Controllers
                 Desc = viewModel.Desc,
                 SpbillCreateIp = viewModel.SpbillCreateIp
             };
-            var response = await _client.ExecuteAsync(request);
+            var response = await _client.ExecuteAsync(request, "wechatpayCertificateName");
             ViewData["response"] = response.Body;
             return View();
         }
@@ -379,7 +378,7 @@ namespace WebApplicationSample.Controllers
             {
                 PartnerTradeNo = viewModel.PartnerTradeNo,
             };
-            var response = await _client.ExecuteAsync(request);
+            var response = await _client.ExecuteAsync(request, "wechatpayCertificateName");
             ViewData["response"] = response.Body;
             return View();
         }
@@ -402,7 +401,7 @@ namespace WebApplicationSample.Controllers
                 Amount = viewModel.Amount,
                 Desc = viewModel.Desc,
             };
-            var response = await _client.ExecuteAsync(request);
+            var response = await _client.ExecuteAsync(request, "wechatpayCertificateName");
             ViewData["response"] = response.Body;
             return View();
         }
@@ -420,7 +419,7 @@ namespace WebApplicationSample.Controllers
             {
                 PartnerTradeNo = viewModel.PartnerTradeNo,
             };
-            var response = await _client.ExecuteAsync(request);
+            var response = await _client.ExecuteAsync(request, "wechatpayCertificateName");
             ViewData["response"] = response.Body;
             return View();
         }
@@ -432,7 +431,7 @@ namespace WebApplicationSample.Controllers
             if (Request.Method == "POST")
             {
                 var request = new WeChatPayGetPublicKeyRequest();
-                var response = await _client.ExecuteAsync(request);
+                var response = await _client.ExecuteAsync(request, "wechatpayCertificateName");
                 ViewData["response"] = response.Body;
                 return View();
             }

+ 25 - 20
samples/WebApplicationSample/Startup.cs

@@ -1,6 +1,4 @@
-using System;
-using System.Security.Cryptography.X509Certificates;
-using System.Text.Encodings.Web;
+using System.Text.Encodings.Web;
 using System.Text.Unicode;
 using Essensoft.AspNetCore.Payment.Alipay;
 using Essensoft.AspNetCore.Payment.JDPay;
@@ -37,30 +35,35 @@ namespace WebApplicationSample
                 options.MinimumSameSitePolicy = SameSiteMode.None;
             });
 
-            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
-
+            // 引入HttpClient
+            services.AddHttpClient();
+
+            //引入HttpClient API证书的使用(仅QPay / WeChatPay的部分API使用到)。
+            //services.AddHttpClient("qpayCertificateName").ConfigurePrimaryHttpMessageHandler(() =>
+            //{
+            //    var certificate = new X509Certificate2("", "", X509KeyStorageFlags.MachineKeySet);
+            //    var handler = new HttpClientHandler();
+            //    handler.ClientCertificates.Add(certificate);
+            //    return handler;
+            //});
+
+            //services.AddHttpClient("wechatpayCertificateName").ConfigurePrimaryHttpMessageHandler(() =>
+            //{
+            //    var certificate = new X509Certificate2("", "", X509KeyStorageFlags.MachineKeySet);
+            //    var handler = new HttpClientHandler();
+            //    handler.ClientCertificates.Add(certificate);
+            //    return handler;
+            //});
+
+            // 引入Payment 依赖注入
             services.AddAlipay();
-            services.AddAlipayHttpClient();
-
             services.AddJDPay();
-            services.AddJDPayHttpClient();
-
             services.AddQPay();
-            services.AddQPayHttpClient();
-            //services.AddQPayCertificateHttpClient(new X509Certificate2(Convert.FromBase64String(Configuration["QPay:Certificate"]), Configuration["QPay:MchId"], X509KeyStorageFlags.MachineKeySet));
-            //services.AddQPayCertificateHttpClient("Default", new X509Certificate2(Convert.FromBase64String(Configuration["QPay:Certificate"]), Configuration["QPay:MchId"], X509KeyStorageFlags.MachineKeySet));
-
             services.AddUnionPay();
-            services.AddUnionPayHttpClient();
-
             services.AddWeChatPay();
-            services.AddWeChatPayHttpClient();
-            //services.AddWeChatPayCertificateHttpClient(new X509Certificate2(Convert.FromBase64String(Configuration["WeChatPay:Certificate"]), Configuration["WeChatPay:MchId"], X509KeyStorageFlags.MachineKeySet));
-            //services.AddWeChatPayCertificateHttpClient("Default", new X509Certificate2(Convert.FromBase64String(Configuration["WeChatPay:Certificate"]), Configuration["WeChatPay:MchId"], X509KeyStorageFlags.MachineKeySet));
-
             services.AddLianLianPay();
-            services.AddLianLianPayHttpClient();
 
+            // 在 appsettings.json 中 配置选项
             services.Configure<AlipayOptions>(Configuration.GetSection("Alipay"));
             services.Configure<JDPayOptions>(Configuration.GetSection("JDPay"));
             services.Configure<QPayOptions>(Configuration.GetSection("QPay"));
@@ -72,6 +75,8 @@ namespace WebApplicationSample
             {
                 opt.TextEncoderSettings = new TextEncoderSettings(UnicodeRanges.All);
             });
+
+            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
         }
 
         // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

+ 86 - 92
src/Essensoft.AspNetCore.Payment.Alipay/AlipayClient.cs

@@ -35,39 +35,24 @@ namespace Essensoft.AspNetCore.Payment.Alipay
         private const string ENCRYPT_TYPE = "encrypt_type";
         private const string BIZ_CONTENT = "biz_content";
         private const string APP_AUTH_TOKEN = "app_auth_token";
-        private const string RETURN_URL = "return_url";        
+        private const string RETURN_URL = "return_url";
 
         public virtual ILogger Logger { get; set; }
 
         public virtual IHttpClientFactory ClientFactory { get; set; }
 
-        public AlipayOptions Options { get; protected set; }
+        public virtual IOptionsSnapshot<AlipayOptions> OptionsSnapshotAccessor { get; set; }
 
         #region AlipayClient Constructors
 
         public AlipayClient(
             ILogger<AlipayClient> logger,
             IHttpClientFactory clientFactory,
-            IOptions<AlipayOptions> optionsAccessor)
+            IOptionsSnapshot<AlipayOptions> optionsAccessor)
         {
             Logger = logger;
             ClientFactory = clientFactory;
-            Options = optionsAccessor.Value;
-
-            if (string.IsNullOrEmpty(Options.AppId))
-            {
-                throw new ArgumentNullException(nameof(Options.AppId));
-            }
-
-            if (string.IsNullOrEmpty(Options.RsaPrivateKey))
-            {
-                throw new ArgumentNullException(nameof(Options.RsaPrivateKey));
-            }
-
-            if (string.IsNullOrEmpty(Options.RsaPublicKey))
-            {
-                throw new ArgumentNullException(nameof(Options.RsaPublicKey));
-            }          
+            OptionsSnapshotAccessor = optionsAccessor;
         }
 
         #endregion
@@ -76,13 +61,17 @@ namespace Essensoft.AspNetCore.Payment.Alipay
 
         public async Task<T> ExecuteAsync<T>(IAlipayRequest<T> request) where T : AlipayResponse
         {
-            return await ExecuteAsync<T>(request, null);
+            return await ExecuteAsync(request, null);
         }
 
-        public async Task<T> ExecuteAsync<T>(IAlipayRequest<T> request, string accessToken) where T : AlipayResponse
+        public async Task<T> ExecuteAsync<T>(IAlipayRequest<T> request, string optionsName) where T : AlipayResponse
         {
+            return await ExecuteAsync(request, optionsName, null, null);
+        }
 
-            return await ExecuteAsync<T>(request, accessToken, null);
+        public async Task<T> ExecuteAsync<T>(IAlipayRequest<T> request, string optionsName, string accessToken) where T : AlipayResponse
+        {
+            return await ExecuteAsync(request, optionsName, accessToken, null);
         }
 
         #endregion
@@ -91,7 +80,12 @@ namespace Essensoft.AspNetCore.Payment.Alipay
 
         public async Task<T> PageExecuteAsync<T>(IAlipayRequest<T> request) where T : AlipayResponse
         {
-            return await PageExecuteAsync<T>(request, null, "POST");
+            return await PageExecuteAsync(request, null, "POST");
+        }
+
+        public async Task<T> PageExecuteAsync<T>(string optionsName, IAlipayRequest<T> request) where T : AlipayResponse
+        {
+            return await PageExecuteAsync(optionsName, request, null, "POST");
         }
 
         #endregion
@@ -100,40 +94,34 @@ namespace Essensoft.AspNetCore.Payment.Alipay
 
         public async Task<T> PageExecuteAsync<T>(IAlipayRequest<T> request, string accessToken, string reqMethod) where T : AlipayResponse
         {
-            string apiVersion = null;
-
-            if (!string.IsNullOrEmpty(request.GetApiVersion()))
-            {
-                apiVersion = request.GetApiVersion();
-            }
-            else
-            {
-                apiVersion = Options.Version;
-            }
+            return await PageExecuteAsync(null, request, accessToken, reqMethod);
+        }
 
+        public async Task<T> PageExecuteAsync<T>(string optionsName, IAlipayRequest<T> request, string accessToken, string reqMethod) where T : AlipayResponse
+        {
+            var options = string.IsNullOrEmpty(optionsName) ? OptionsSnapshotAccessor.Value : OptionsSnapshotAccessor.Get(optionsName);
+            var apiVersion = string.IsNullOrEmpty(request.GetApiVersion()) ? options.Version : request.GetApiVersion();
             var txtParams = new AlipayDictionary(request.GetParameters())
             {
-                // 序列化BizModel
                 { BIZ_CONTENT, Serialize(request.GetBizModel()) },
-                // 添加协议级请求参数
                 { METHOD, request.GetApiName() },
                 { VERSION, apiVersion },
-                { APP_ID, Options.AppId },
-                { FORMAT, Options.Format },
+                { APP_ID, options.AppId },
+                { FORMAT, options.Format },
                 { TIMESTAMP, DateTime.Now },
                 { ACCESS_TOKEN, accessToken },
-                { SIGN_TYPE, Options.SignType },
+                { SIGN_TYPE, options.SignType },
                 { TERMINAL_TYPE, request.GetTerminalType() },
                 { TERMINAL_INFO, request.GetTerminalInfo() },
                 { PROD_CODE, request.GetProdCode() },
                 { NOTIFY_URL, request.GetNotifyUrl() },
-                { CHARSET, Options.Charset },
+                { CHARSET, options.Charset },
                 { RETURN_URL, request.GetReturnUrl() }
             };
 
             // 添加签名参数
             var signContent = AlipaySignature.GetSignContent(txtParams);
-            txtParams.Add(SIGN, AlipaySignature.RSASignContent(signContent, Options.PrivateRSAParameters, Options.SignType));
+            txtParams.Add(SIGN, AlipaySignature.RSASignContent(signContent, options.PrivateRSAParameters, options.SignType));
 
             // 是否需要上传文件
             var body = string.Empty;
@@ -142,18 +130,17 @@ namespace Essensoft.AspNetCore.Payment.Alipay
             {
                 var fileParams = AlipayUtility.CleanupDictionary(uRequest.GetFileParameters());
 
-                using (var client = ClientFactory.CreateClient(AlipayOptions.DefaultClientName))
+                using (var client = ClientFactory.CreateClient())
                 {
-                    body = await HttpClientUtility.DoPostAsync(client, Options.ServerUrl, txtParams, fileParams);
+                    body = await HttpClientUtility.DoPostAsync(client, options.ServerUrl, txtParams, fileParams);
                 }
             }
             else
             {
-
                 if (reqMethod.ToUpper() == "GET")
                 {
                     //拼接get请求的url
-                    var tmpUrl = Options.ServerUrl;
+                    var tmpUrl = options.ServerUrl;
                     if (txtParams != null && txtParams.Count > 0)
                     {
                         if (tmpUrl.Contains("?"))
@@ -171,14 +158,14 @@ namespace Essensoft.AspNetCore.Payment.Alipay
                 else
                 {
                     //输出post表单
-                    body = BuildHtmlRequest(txtParams, reqMethod);
+                    body = BuildHtmlRequest(txtParams, reqMethod, options);
                     Logger?.LogTrace(0, "Request Html:{body}", body);
                 }
             }
 
             T rsp = null;
             IAlipayParser<T> parser = null;
-            if ("xml".Equals(Options.Format))
+            if ("xml".Equals(options.Format))
             {
                 parser = new AlipayXmlParser<T>();
                 rsp = parser.Parse(body);
@@ -195,36 +182,24 @@ namespace Essensoft.AspNetCore.Payment.Alipay
 
         #region IAlipayClient Members
 
-        public async Task<T> ExecuteAsync<T>(IAlipayRequest<T> request, string accessToken, string appAuthToken) where T : AlipayResponse
+        public async Task<T> ExecuteAsync<T>(IAlipayRequest<T> request, string optionsName, string accessToken, string appAuthToken) where T : AlipayResponse
         {
-            var apiVersion = string.Empty;
-
-            if (!string.IsNullOrEmpty(request.GetApiVersion()))
-            {
-                apiVersion = request.GetApiVersion();
-            }
-            else
-            {
-                apiVersion = Options.Version;
-            }
-
-            // 添加协议级请求参数
+            var options = string.IsNullOrEmpty(optionsName) ? OptionsSnapshotAccessor.Value : OptionsSnapshotAccessor.Get(optionsName);
+            var apiVersion = string.IsNullOrEmpty(request.GetApiVersion()) ? options.Version : request.GetApiVersion();
             var txtParams = new AlipayDictionary(request.GetParameters())
             {
-                // 序列化BizModel
                 { BIZ_CONTENT, Serialize(request.GetBizModel()) },
-                // 添加协议级请求参数
                 { METHOD, request.GetApiName() },
                 { VERSION, apiVersion },
-                { APP_ID, Options.AppId },
-                { FORMAT, Options.Format },
+                { APP_ID, options.AppId },
+                { FORMAT, options.Format },
                 { TIMESTAMP, DateTime.Now },
                 { ACCESS_TOKEN, accessToken },
-                { SIGN_TYPE, Options.SignType },
+                { SIGN_TYPE, options.SignType },
                 { TERMINAL_TYPE, request.GetTerminalType() },
                 { TERMINAL_INFO, request.GetTerminalInfo() },
                 { PROD_CODE, request.GetProdCode() },
-                { CHARSET, Options.Charset }
+                { CHARSET, options.Charset }
             };
 
             if (!string.IsNullOrEmpty(request.GetNotifyUrl()))
@@ -245,43 +220,43 @@ namespace Essensoft.AspNetCore.Payment.Alipay
                     throw new Exception("api request Fail ! The reason: encrypt request is not supported!");
                 }
 
-                if (string.IsNullOrEmpty(Options.EncyptKey) || string.IsNullOrEmpty(Options.EncyptType))
+                if (string.IsNullOrEmpty(options.EncyptKey) || string.IsNullOrEmpty(options.EncyptType))
                 {
                     throw new Exception("encryptType or encryptKey must not null!");
                 }
 
-                if (!"AES".Equals(Options.EncyptType))
+                if (!"AES".Equals(options.EncyptType))
                 {
                     throw new Exception("api only support Aes!");
 
                 }
 
-                var encryptContent = AES.Encrypt(txtParams[BIZ_CONTENT], Options.EncyptKey, AlipaySignature.AES_IV, AESCipherMode.CBC, AESPaddingMode.PKCS7);
+                var encryptContent = AES.Encrypt(txtParams[BIZ_CONTENT], options.EncyptKey, AlipaySignature.AES_IV, AESCipherMode.CBC, AESPaddingMode.PKCS7);
                 txtParams.Remove(BIZ_CONTENT);
                 txtParams.Add(BIZ_CONTENT, encryptContent);
-                txtParams.Add(ENCRYPT_TYPE, Options.EncyptType);
+                txtParams.Add(ENCRYPT_TYPE, options.EncyptType);
             }
 
             // 添加签名参数
             var signContent = AlipaySignature.GetSignContent(txtParams);
-            txtParams.Add(SIGN, AlipaySignature.RSASignContent(signContent, Options.PrivateRSAParameters, Options.SignType));
+            txtParams.Add(SIGN, AlipaySignature.RSASignContent(signContent, options.PrivateRSAParameters, options.SignType));
 
             var query = AlipayUtility.BuildQuery(txtParams);
             Logger?.LogTrace(0, "Request:{query}", query);
 
             // 是否需要上传文件
             var body = string.Empty;
-            using (var client = ClientFactory.CreateClient(AlipayOptions.DefaultClientName))
+            using (var client = ClientFactory.CreateClient())
             {
                 if (request is IAlipayUploadRequest<T> uRequest)
                 {
                     var fileParams = AlipayUtility.CleanupDictionary(uRequest.GetFileParameters());
 
-                    body = await HttpClientUtility.DoPostAsync(client, Options.ServerUrl, txtParams, fileParams);
+                    body = await HttpClientUtility.DoPostAsync(client, options.ServerUrl, txtParams, fileParams);
                 }
                 else
                 {
-                    body = await HttpClientUtility.DoPostAsync(client, Options.ServerUrl, query);
+                    body = await HttpClientUtility.DoPostAsync(client, options.ServerUrl, query);
                 }
             }
 
@@ -289,7 +264,7 @@ namespace Essensoft.AspNetCore.Payment.Alipay
 
             T rsp = null;
             IAlipayParser<T> parser = null;
-            if ("xml".Equals(Options.Format))
+            if ("xml".Equals(options.Format))
             {
                 parser = new AlipayXmlParser<T>();
                 rsp = parser.Parse(body);
@@ -300,10 +275,10 @@ namespace Essensoft.AspNetCore.Payment.Alipay
                 rsp = parser.Parse(body);
             }
 
-            var item = ParseRespItem(request, body, parser, Options.EncyptKey, Options.EncyptType);
+            var item = ParseRespItem(request, body, parser, options.EncyptKey, options.EncyptType);
             rsp = parser.Parse(item.realContent);
 
-            CheckResponseSign(request, item.respContent, rsp.IsError, parser, Options.PublicRSAParameters, Options.SignType);
+            CheckResponseSign(request, item.respContent, rsp.IsError, parser, options.PublicRSAParameters, options.SignType);
 
             return rsp;
         }
@@ -364,13 +339,14 @@ namespace Essensoft.AspNetCore.Payment.Alipay
         #endregion
 
         #region IAlipayClient Members
-        public string BuildHtmlRequest(IDictionary<string, string> sParaTemp, string strMethod)
+
+        public string BuildHtmlRequest(IDictionary<string, string> sParaTemp, string strMethod, AlipayOptions options)
         {
             //待请求参数数组
             var dicPara = new Dictionary<string, string>(sParaTemp);
 
             var sbHtml = new StringBuilder();
-            sbHtml.Append("<form id='submit' name='submit' action='" + Options.ServerUrl + "?charset=" + Options.Charset +
+            sbHtml.Append("<form id='submit' name='submit' action='" + options.ServerUrl + "?charset=" + options.Charset +
                  "' method='" + strMethod + "' style='display:none;'>");
 
             foreach (var temp in dicPara)
@@ -383,14 +359,22 @@ namespace Essensoft.AspNetCore.Payment.Alipay
 
             return sbHtml.ToString();
         }
+
         #endregion
 
         #region SDK Execute
 
         public Task<T> SdkExecuteAsync<T>(IAlipayRequest<T> request) where T : AlipayResponse
         {
+            return SdkExecuteAsync(request, null);
+        }
+
+        public Task<T> SdkExecuteAsync<T>(IAlipayRequest<T> request, string optionsName) where T : AlipayResponse
+        {
+            var options = string.IsNullOrEmpty(optionsName) ? OptionsSnapshotAccessor.Value : OptionsSnapshotAccessor.Get(optionsName);
+
             // 构造请求参数
-            var requestParams = BuildRequestParams(request, null, null);
+            var requestParams = BuildRequestParams(request, null, null, options);
 
             // 字典排序
             var sortedParams = new SortedDictionary<string, string>(requestParams);
@@ -398,7 +382,7 @@ namespace Essensoft.AspNetCore.Payment.Alipay
 
             // 参数签名
             var signContent = AlipaySignature.GetSignContent(sortedAlipayDic);
-            var signResult = AlipaySignature.RSASignContent(signContent, Options.PrivateRSAParameters, Options.SignType);
+            var signResult = AlipaySignature.RSASignContent(signContent, options.PrivateRSAParameters, options.SignType);
 
             // 添加签名结果参数
             sortedAlipayDic.Add(SIGN, signResult);
@@ -416,26 +400,26 @@ namespace Essensoft.AspNetCore.Payment.Alipay
 
         #region Common Method
 
-        private AlipayDictionary BuildRequestParams<T>(IAlipayRequest<T> request, string accessToken, string appAuthToken) where T : AlipayResponse
+        private AlipayDictionary BuildRequestParams<T>(IAlipayRequest<T> request, string accessToken, string appAuthToken, AlipayOptions options) where T : AlipayResponse
         {
-            // 默认参数
+            var apiVersion = string.IsNullOrEmpty(request.GetApiVersion()) ? options.Version : request.GetApiVersion();
             var result = new AlipayDictionary(request.GetParameters())
             {
                 // 序列化BizModel
                 { BIZ_CONTENT, Serialize(request.GetBizModel()) },
                 // 添加协议级请求参数,为空的参数后面会自动过滤,这里不做处理。
                 { METHOD, request.GetApiName() },
-                { VERSION, string.IsNullOrEmpty(request.GetApiVersion()) ? Options.Version : request.GetApiVersion() },
-                { APP_ID, Options.AppId },
-                { FORMAT, Options.Format },
+                { VERSION, apiVersion },
+                { APP_ID, options.AppId },
+                { FORMAT, options.Format },
                 { TIMESTAMP, DateTime.Now },
                 { ACCESS_TOKEN, accessToken },
-                { SIGN_TYPE, Options.SignType },
+                { SIGN_TYPE, options.SignType },
                 { TERMINAL_TYPE, request.GetTerminalType() },
                 { TERMINAL_INFO, request.GetTerminalInfo() },
                 { PROD_CODE, request.GetProdCode() },
                 { NOTIFY_URL, request.GetNotifyUrl() },
-                { CHARSET, Options.Charset },
+                { CHARSET, options.Charset },
                 { RETURN_URL, request.GetReturnUrl() },
                 { APP_AUTH_TOKEN, appAuthToken }
             };
@@ -447,20 +431,20 @@ namespace Essensoft.AspNetCore.Payment.Alipay
                     throw new Exception("api request Fail ! The reason: encrypt request is not supported!");
                 }
 
-                if (string.IsNullOrEmpty(Options.EncyptKey) || string.IsNullOrEmpty(Options.EncyptType))
+                if (string.IsNullOrEmpty(options.EncyptKey) || string.IsNullOrEmpty(options.EncyptType))
                 {
                     throw new Exception("encryptType or encryptKey must not null!");
                 }
 
-                if (!"AES".Equals(Options.EncyptType))
+                if (!"AES".Equals(options.EncyptType))
                 {
                     throw new Exception("api only support Aes!");
                 }
 
-                var encryptContent = AES.Encrypt(result[BIZ_CONTENT], Options.EncyptKey, AlipaySignature.AES_IV, AESCipherMode.CBC, AESPaddingMode.PKCS7);
+                var encryptContent = AES.Encrypt(result[BIZ_CONTENT], options.EncyptKey, AlipaySignature.AES_IV, AESCipherMode.CBC, AESPaddingMode.PKCS7);
                 result.Remove(BIZ_CONTENT);
                 result.Add(BIZ_CONTENT, encryptContent);
-                result.Add(ENCRYPT_TYPE, Options.EncyptType);
+                result.Add(ENCRYPT_TYPE, options.EncyptType);
             }
 
             return result;
@@ -470,12 +454,22 @@ namespace Essensoft.AspNetCore.Payment.Alipay
 
         #region Model Serialize
 
-        static readonly JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
+        private static readonly JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
         private string Serialize(AlipayObject bizModel)
         {
             return bizModel == null ? string.Empty : JsonConvert.SerializeObject(bizModel, jsonSerializerSettings);
         }
 
+        public Task<T> PageExecuteAsync<T>(IAlipayRequest<T> request, string optionsName) where T : AlipayResponse
+        {
+            throw new NotImplementedException();
+        }
+
+        public Task<T> PageExecuteAsync<T>(IAlipayRequest<T> request, string optionsName, string session, string reqMethod) where T : AlipayResponse
+        {
+            throw new NotImplementedException();
+        }
+
         #endregion
     }
 }

+ 10 - 9
src/Essensoft.AspNetCore.Payment.Alipay/AlipayNotifyClient.cs

@@ -15,21 +15,16 @@ namespace Essensoft.AspNetCore.Payment.Alipay
     {
         public virtual ILogger Logger { get; set; }
 
-        public AlipayOptions Options { get; protected set; }
+        public virtual IOptionsSnapshot<AlipayOptions> OptionsSnapshotAccessor { get; set; }
 
         #region AlipayNotifyClient Constructors
 
         public AlipayNotifyClient(
             ILogger<AlipayNotifyClient> logger,
-            IOptions<AlipayOptions> optionsAccessor)
+            IOptionsSnapshot<AlipayOptions> optionsAccessor)
         {
             Logger = logger;
-            Options = optionsAccessor.Value;
-
-            if (string.IsNullOrEmpty(Options.RsaPublicKey))
-            {
-                throw new ArgumentNullException(nameof(Options.RsaPublicKey));
-            }            
+            OptionsSnapshotAccessor = optionsAccessor;
         }
 
         #endregion
@@ -38,13 +33,19 @@ namespace Essensoft.AspNetCore.Payment.Alipay
 
         public async Task<T> ExecuteAsync<T>(HttpRequest request) where T : AlipayNotifyResponse
         {
+            return await ExecuteAsync<T>(request, null);
+        }
+
+        public async Task<T> ExecuteAsync<T>(HttpRequest request, string optionsName) where T : AlipayNotifyResponse
+        {
+            var options = OptionsSnapshotAccessor.Get(optionsName);
             var parameters = await GetParametersAsync(request);
             var query = AlipayUtility.BuildQuery(parameters);
             Logger?.LogTrace(0, "Request:{query}", query);
 
             var parser = new AlipayDictionaryParser<T>();
             var rsp = parser.Parse(parameters);
-            CheckNotifySign(parameters, Options.PublicRSAParameters, Options.SignType);
+            CheckNotifySign(parameters, options.PublicRSAParameters, options.SignType);
             return rsp;
         }
 

+ 0 - 2
src/Essensoft.AspNetCore.Payment.Alipay/AlipayOptions.cs

@@ -5,8 +5,6 @@ namespace Essensoft.AspNetCore.Payment.Alipay
 {
     public class AlipayOptions
     {
-        public static readonly string DefaultClientName = "Payment.Alipay.Client";
-
         /// <summary>
         /// 应用ID
         /// </summary>

+ 0 - 14
src/Essensoft.AspNetCore.Payment.Alipay/AlipayOptionsAccessor.cs

@@ -1,14 +0,0 @@
-using Microsoft.Extensions.Options;
-
-namespace Essensoft.AspNetCore.Payment.Alipay
-{
-    public class AlipayOptionsAccessor : IOptions<AlipayOptions>
-    {
-        public AlipayOptionsAccessor(AlipayOptions options)
-        {
-            Value = options;
-        }
-
-        public AlipayOptions Value { get; }
-    }
-}

+ 3 - 3
src/Essensoft.AspNetCore.Payment.Alipay/Essensoft.AspNetCore.Payment.Alipay.csproj

@@ -5,9 +5,9 @@
     <Company>Essensoft</Company>
     <Authors>Roc</Authors>
     <Product>Payment</Product>
-    <Version>1.6.2</Version>
-    <AssemblyVersion>1.6.2.0</AssemblyVersion>
-    <FileVersion>1.6.2.0</FileVersion>
+    <Version>2.0.0</Version>
+    <AssemblyVersion>2.0.0.0</AssemblyVersion>
+    <FileVersion>2.0.0.0</FileVersion>
     <Description>Essensoft.AspNetCore.Payment.Alipay</Description>
     <Copyright>© Essensoft 2018</Copyright>
     <PackageProjectUrl>https://github.com/Essensoft/Payment</PackageProjectUrl>

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

@@ -16,24 +16,35 @@ namespace Essensoft.AspNetCore.Payment.Alipay
         /// <returns>领域对象</returns>
         Task<T> ExecuteAsync<T>(IAlipayRequest<T> request) where T : AlipayResponse;
 
+        /// <summary>
+        /// 执行Alipay公开API请求。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">具体的Alipay API请求</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteAsync<T>(IAlipayRequest<T> request, string optionsName) where T : AlipayResponse;
+
         /// <summary>
         /// 执行Alipay隐私API请求。
         /// </summary>
         /// <typeparam name="T">领域对象</typeparam>
         /// <param name="request">具体的Alipay API请求</param>
         /// <param name="session">用户会话码</param>
+        /// <param name="appAuthToken">应用授权码</param>
         /// <returns>领域对象</returns>
-        Task<T> ExecuteAsync<T>(IAlipayRequest<T> request, string session) where T : AlipayResponse;
+        Task<T> ExecuteAsync<T>(IAlipayRequest<T> request, string session, string appAuthToken) where T : AlipayResponse;
 
         /// <summary>
         /// 执行Alipay隐私API请求。
         /// </summary>
         /// <typeparam name="T">领域对象</typeparam>
         /// <param name="request">具体的Alipay API请求</param>
+        /// <param name="optionsName">配置选项名称</param>
         /// <param name="session">用户会话码</param>
         /// <param name="appAuthToken">应用授权码</param>
         /// <returns>领域对象</returns>
-        Task<T> ExecuteAsync<T>(IAlipayRequest<T> request, string session, string appAuthToken) where T : AlipayResponse;
+        Task<T> ExecuteAsync<T>(IAlipayRequest<T> request, string optionsName, string session, string appAuthToken) where T : AlipayResponse;
 
         /// <summary>
         /// 执行Alipay公开API请求。
@@ -43,6 +54,15 @@ namespace Essensoft.AspNetCore.Payment.Alipay
         /// <returns>领域对象</returns>
         Task<T> PageExecuteAsync<T>(IAlipayRequest<T> request) where T : AlipayResponse;
 
+        /// <summary>
+        /// 执行Alipay公开API请求。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">具体的Alipay API请求</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <returns>领域对象</returns>
+        Task<T> PageExecuteAsync<T>(IAlipayRequest<T> request, string optionsName) where T : AlipayResponse;
+
         /// <summary>
         /// 执行Alipay隐私API请求。
         /// </summary>
@@ -52,6 +72,16 @@ namespace Essensoft.AspNetCore.Payment.Alipay
         /// <returns>领域对象</returns>
         Task<T> PageExecuteAsync<T>(IAlipayRequest<T> request, string session, string reqMethod) where T : AlipayResponse;
 
+        /// <summary>
+        /// 执行Alipay隐私API请求。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">具体的Alipay API请求</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <param name="session">用户会话码</param>
+        /// <returns>领域对象</returns>
+        Task<T> PageExecuteAsync<T>(IAlipayRequest<T> request, string optionsName, string session, string reqMethod) where T : AlipayResponse;
+
         /// <summary>
         /// 执行Alipay公开API请求。
         /// </summary>
@@ -59,5 +89,14 @@ namespace Essensoft.AspNetCore.Payment.Alipay
         /// <param name="request">具体的Alipay API请求</param>
         /// <returns>领域对象</returns>
         Task<T> SdkExecuteAsync<T>(IAlipayRequest<T> request) where T : AlipayResponse;
+
+        /// <summary>
+        /// 执行Alipay公开API请求。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">具体的Alipay API请求</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <returns>领域对象</returns>
+        Task<T> SdkExecuteAsync<T>(IAlipayRequest<T> request, string optionsName) where T : AlipayResponse;
     }
 }

+ 16 - 1
src/Essensoft.AspNetCore.Payment.Alipay/IAlipayNotifyClient.cs

@@ -4,10 +4,25 @@ using Microsoft.AspNetCore.Http;
 namespace Essensoft.AspNetCore.Payment.Alipay
 {
     /// <summary>
-    /// Alipay通知客户端。
+    /// Alipay通知解析客户端。
     /// </summary>
     public interface IAlipayNotifyClient
     {
+        /// <summary>
+        /// 执行Alipay通知请求解析。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">控制器的请求</param>
+        /// <returns>领域对象</returns>
         Task<T> ExecuteAsync<T>(HttpRequest request) where T : AlipayNotifyResponse;
+
+        /// <summary>
+        /// 执行Alipay通知请求解析。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">控制器的请求</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteAsync<T>(HttpRequest request, string optionsName) where T : AlipayNotifyResponse;
     }
 }

+ 1 - 1
src/Essensoft.AspNetCore.Payment.Alipay/Request/AlipayFlashsalesStockSyncUpdateRequest.cs

@@ -22,7 +22,7 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Request
         /// <summary>
         /// 库存数量
         /// </summary>
-        public Nullable<long> Stock { get; set; }
+        public long? Stock { get; set; }
 
         #region IAlipayRequest Members
 		private bool  needEncrypt=false;

+ 1 - 1
src/Essensoft.AspNetCore.Payment.Alipay/Request/AlipayMicropayOrderFreezeRequest.cs

@@ -17,7 +17,7 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Request
         /// <summary>
         /// 冻结资金的到期时间,超过此日期,冻结金会自动解冻,时间要求是:[当前时间+24h,订购时间-8h] .
         /// </summary>
-        public Nullable<DateTime> ExpireTime { get; set; }
+        public DateTime? ExpireTime { get; set; }
 
         /// <summary>
         /// 冻结备注,maxLength=40

+ 3 - 3
src/Essensoft.AspNetCore.Payment.Alipay/Request/AlipayMobileCodeCreateRequest.cs

@@ -27,7 +27,7 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Request
         /// <summary>
         /// 如果是true,则扫一扫下发跳转地址直接取自bizLinkedId  否则,从路由信息里取跳转地址
         /// </summary>
-        public Nullable<bool> IsDirect { get; set; }
+        public bool? IsDirect { get; set; }
 
         /// <summary>
         /// 备注信息字段
@@ -42,12 +42,12 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Request
         /// <summary>
         /// 编码启动时间(yyy-MM-dd hh:mm:ss),为空表示立即启用
         /// </summary>
-        public Nullable<DateTime> StartDate { get; set; }
+        public DateTime? StartDate { get; set; }
 
         /// <summary>
         /// 超时时间,单位秒;若不传则为永久。发码超时时间需要找码平台技术评估
         /// </summary>
-        public Nullable<long> Timeout { get; set; }
+        public long? Timeout { get; set; }
 
         /// <summary>
         /// 支付宝用户id

+ 1 - 1
src/Essensoft.AspNetCore.Payment.Alipay/Request/AlipayOpenAgentFacetofaceSignRequest.cs

@@ -38,7 +38,7 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Request
         /// <summary>
         /// 营业期限是否长期有效
         /// </summary>
-        public Nullable<bool> LongTerm { get; set; }
+        public bool? LongTerm { get; set; }
 
         /// <summary>
         /// 所属MCCCode,详情可参考  <a href="https://doc.open.alipay.com/doc2/detail.htm?spm=a219a.7629140.0.0.59bgD2&treeId=222&articleId=105364&docType=1#s1  ">商家经营类目</a> 中的“经营类目编码”

+ 1 - 1
src/Essensoft.AspNetCore.Payment.Alipay/Request/AlipayOpenAgentMobilepaySignRequest.cs

@@ -48,7 +48,7 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Request
         /// <summary>
         /// 营业期限是否长期有效
         /// </summary>
-        public Nullable<bool> LongTerm { get; set; }
+        public bool? LongTerm { get; set; }
 
         /// <summary>
         /// 所属MCCCode,详情可参考  <a href="https://doc.open.alipay.com/doc2/detail.htm?spm=a219a.7629140.0.0.59bgD2&treeId=222&articleId=105364&docType=1#s1  ">商家经营类目</a> 中的“经营类目编码”

+ 1 - 1
src/Essensoft.AspNetCore.Payment.Alipay/Request/AlipayOpenAgentZhimabriefSignRequest.cs

@@ -74,7 +74,7 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Request
         /// <summary>
         /// 营业期限是否长期有效
         /// </summary>
-        public Nullable<bool> LongTerm { get; set; }
+        public bool? LongTerm { get; set; }
 
         /// <summary>
         /// 所属MCCCode,详情可参考  <a href="https://doc.open.alipay.com/doc2/detail.htm?spm=a219a.7629140.0.0.59bgD2&treeId=222&articleId=105364&docType=1#s1  ">商家经营类目</a> 中的“经营类目编码”

+ 2 - 2
src/Essensoft.AspNetCore.Payment.Alipay/Request/AlipayPointOrderAddRequest.cs

@@ -22,12 +22,12 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Request
         /// <summary>
         /// 发放集分宝时间
         /// </summary>
-        public Nullable<DateTime> OrderTime { get; set; }
+        public DateTime? OrderTime { get; set; }
 
         /// <summary>
         /// 发放集分宝的数量
         /// </summary>
-        public Nullable<long> PointCount { get; set; }
+        public long? PointCount { get; set; }
 
         /// <summary>
         /// 用户标识符,用于指定集分宝发放的用户,和user_symbol_type一起使用,确定一个唯一的支付宝用户

+ 1 - 1
src/Essensoft.AspNetCore.Payment.Alipay/Request/AlipayZdatafrontCommonQueryRequest.cs

@@ -12,7 +12,7 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Request
         /// <summary>
         /// 如果cacheInterval<=0,就直接从外部获取数据;  如果cacheInterval>0,就先判断cache中的数据是否过期,如果没有过期就返回cache中的数据,如果过期再从外部获取数据并刷新cache,然后返回数据。  单位:秒
         /// </summary>
-        public Nullable<long> CacheInterval { get; set; }
+        public long? CacheInterval { get; set; }
 
         /// <summary>
         /// 通用查询的入参

+ 1 - 1
src/Essensoft.AspNetCore.Payment.Alipay/Request/AlipayZdatafrontDatatransferedFileuploadRequest.cs

@@ -43,7 +43,7 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Request
         /// <summary>
         /// 上传数据文件包含的记录数,file_type为json_data时必选
         /// </summary>
-        public Nullable<long> Records { get; set; }
+        public long? Records { get; set; }
 
         /// <summary>
         /// 外部公司的数据源标识信息,由联接网络分配

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

@@ -15,18 +15,12 @@ namespace Microsoft.Extensions.DependencyInjection
             this IServiceCollection services,
             Action<AlipayOptions> setupAction)
         {
-            services.AddSingleton<AlipayClient>();
-            services.AddSingleton<AlipayNotifyClient>();
+            services.AddScoped<IAlipayClient, AlipayClient>();
+            services.AddScoped<IAlipayNotifyClient, AlipayNotifyClient>();
             if (setupAction != null)
             {
                 services.Configure(setupAction);
             }
         }
-
-        public static void AddAlipayHttpClient(
-            this IServiceCollection services)
-        {
-            services.AddHttpClient(AlipayOptions.DefaultClientName);
-        }
     }
 }

+ 3 - 3
src/Essensoft.AspNetCore.Payment.JDPay/Essensoft.AspNetCore.Payment.JDPay.csproj

@@ -5,9 +5,9 @@
     <Company>Essensoft</Company>
     <Authors>Roc</Authors>
     <Product>Payment</Product>
-    <Version>1.6.2</Version>
-    <AssemblyVersion>1.6.2.0</AssemblyVersion>
-    <FileVersion>1.6.2.0</FileVersion>
+    <Version>2.0.0</Version>
+    <AssemblyVersion>2.0.0.0</AssemblyVersion>
+    <FileVersion>2.0.0.0</FileVersion>
     <Description>Essensoft.AspNetCore.Payment.JDPay</Description>
     <Copyright>© Essensoft 2018</Copyright>
     <PackageProjectUrl>https://github.com/Essensoft/Payment</PackageProjectUrl>

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

@@ -2,6 +2,9 @@
 
 namespace Essensoft.AspNetCore.Payment.JDPay
 {
+    /// <summary>
+    /// JDPay客户端。
+    /// </summary>
     public interface IJDPayClient
     {
         /// <summary>
@@ -11,6 +14,14 @@ namespace Essensoft.AspNetCore.Payment.JDPay
         /// <returns>领域对象</returns>
         Task<T> ExecuteAsync<T>(IJDPayRequest<T> request) where T : JDPayResponse;
 
+        /// <summary>
+        /// 执行JDPay API请求。
+        /// </summary>
+        /// <param name="request">具体的JDPay API请求</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteAsync<T>(IJDPayRequest<T> request, string optionsName) where T : JDPayResponse;
+
         /// <summary>
         /// 执行JDPay API请求。
         /// </summary>
@@ -19,11 +30,28 @@ namespace Essensoft.AspNetCore.Payment.JDPay
         /// <returns></returns>
         Task<T> PageExecuteAsync<T>(IJDPayRequest<T> request) where T : JDPayResponse;
 
+        /// <summary>
+        /// 执行JDPay API请求。
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="request">具体的JDPay API请求</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <returns></returns>
+        Task<T> PageExecuteAsync<T>(IJDPayRequest<T> request, string optionsName) where T : JDPayResponse;
+
         /// <summary>
         /// 执行JDPay API请求。
         /// </summary>
         /// <param name="request">具体的JDPay API请求</param>
         /// <returns>领域对象</returns>
         Task<T> ExecuteAsync<T>(IJDPayNPP10Request<T> request) where T : JDPayResponse;
+
+        /// <summary>
+        /// 执行JDPay API请求。
+        /// </summary>
+        /// <param name="request">具体的JDPay API请求</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteAsync<T>(IJDPayNPP10Request<T> request, string optionsName) where T : JDPayResponse;
     }
 }

+ 18 - 0
src/Essensoft.AspNetCore.Payment.JDPay/IJDPayNotifyClient.cs

@@ -3,8 +3,26 @@ using Microsoft.AspNetCore.Http;
 
 namespace Essensoft.AspNetCore.Payment.JDPay
 {
+    /// <summary>
+    /// JDPay通知解析客户端。
+    /// </summary>
     public interface IJDPayNotifyClient
     {
+        /// <summary>
+        /// 执行JDPay通知请求解析。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">控制器的请求</param>
+        /// <returns>领域对象</returns>
         Task<T> ExecuteAsync<T>(HttpRequest request) where T : JDPayNotifyResponse;
+
+        /// <summary>
+        /// 执行JDPay通知请求解析。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">控制器的请求</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteAsync<T>(HttpRequest request, string optionsName) where T : JDPayNotifyResponse;
     }
 }

+ 41 - 43
src/Essensoft.AspNetCore.Payment.JDPay/JDPayClient.cs

@@ -20,38 +20,18 @@ namespace Essensoft.AspNetCore.Payment.JDPay
 
         public virtual IHttpClientFactory ClientFactory { get; set; }
 
-        public JDPayOptions Options { get; protected set; }
+        public virtual IOptionsSnapshot<JDPayOptions> OptionsSnapshotAccessor { get; set; }
 
         #region JDPayClient Constructors
 
         public JDPayClient(
-            IOptions<JDPayOptions> optionsAccessor,
             ILogger<JDPayClient> logger,
-            IHttpClientFactory clientFactory)
+            IHttpClientFactory clientFactory,
+            IOptionsSnapshot<JDPayOptions> optionsAccessor)
         {
             Logger = logger;
             ClientFactory = clientFactory;
-            Options = optionsAccessor.Value;
-
-            if (string.IsNullOrEmpty(Options.Merchant))
-            {
-                throw new ArgumentNullException(nameof(Options.Merchant));
-            }
-
-            if (string.IsNullOrEmpty(Options.RsaPrivateKey))
-            {
-                throw new ArgumentNullException(nameof(Options.RsaPrivateKey));
-            }
-
-            if (string.IsNullOrEmpty(Options.RsaPublicKey))
-            {
-                throw new ArgumentNullException(nameof(Options.RsaPublicKey));
-            }
-
-            if (string.IsNullOrEmpty(Options.DesKey))
-            {
-                throw new ArgumentNullException(nameof(Options.DesKey));
-            }         
+            OptionsSnapshotAccessor = optionsAccessor;
         }
 
         #endregion
@@ -60,13 +40,19 @@ namespace Essensoft.AspNetCore.Payment.JDPay
 
         public async Task<T> ExecuteAsync<T>(IJDPayRequest<T> request) where T : JDPayResponse
         {
+            return await ExecuteAsync(request, null);
+        }
+
+        public async Task<T> ExecuteAsync<T>(IJDPayRequest<T> request, string optionsName) where T : JDPayResponse
+        {
+            var options = string.IsNullOrEmpty(optionsName) ? OptionsSnapshotAccessor.Value : OptionsSnapshotAccessor.Get(optionsName);
             // 字典排序
             var sortedTxtParams = new JDPayDictionary(request.GetParameters());
 
-            var content = BuildEncryptXml(request, sortedTxtParams);
+            var content = BuildEncryptXml(request, sortedTxtParams, options);
             Logger?.LogTrace(0, "Request:{content}", content);
 
-            using (var client = ClientFactory.CreateClient(JDPayOptions.DefaultClientName))
+            using (var client = ClientFactory.CreateClient())
             {
                 var body = await HttpClientUtility.DoPostAsync(client, request.GetRequestUrl(), content);
                 Logger?.LogTrace(1, "Response:{content}", body);
@@ -77,7 +63,7 @@ namespace Essensoft.AspNetCore.Payment.JDPay
                 {
                     var encrypt = rsp.Encrypt;
                     var base64EncryptStr = Encoding.UTF8.GetString(Convert.FromBase64String(encrypt));
-                    var reqBody = JDPaySecurity.DecryptECB(base64EncryptStr, Options.DesKeyBase64);
+                    var reqBody = JDPaySecurity.DecryptECB(base64EncryptStr, options.DesKeyBase64);
                     Logger?.LogTrace(2, "Encrypt Content:{body}", reqBody);
 
                     var reqBodyDoc = new XmlDocument() { XmlResolver = null };
@@ -95,7 +81,7 @@ namespace Essensoft.AspNetCore.Payment.JDPay
                         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), Options.PublicKey);
+                    var decryptByte = RSA_ECB_PKCS1Padding.Decrypt(Convert.FromBase64String(sign), options.PublicKey);
                     var decryptStr = JDPaySecurity.BytesToString(decryptByte);
                     if (sha256SourceSignString == decryptStr)
                     {
@@ -117,9 +103,15 @@ namespace Essensoft.AspNetCore.Payment.JDPay
 
         public Task<T> PageExecuteAsync<T>(IJDPayRequest<T> request) where T : JDPayResponse
         {
+            return PageExecuteAsync(request, null);
+        }
+
+        public Task<T> PageExecuteAsync<T>(IJDPayRequest<T> request, string optionsName) where T : JDPayResponse
+        {
+            var options = string.IsNullOrEmpty(optionsName) ? OptionsSnapshotAccessor.Value : OptionsSnapshotAccessor.Get(optionsName);
             // 字典排序
             var sortedTxtParams = new JDPayDictionary(request.GetParameters());
-            var encyptParams = BuildEncryptDic(request, sortedTxtParams);
+            var encyptParams = BuildEncryptDic(request, sortedTxtParams, options);
             var rsp = Activator.CreateInstance<T>();
 
             //输出post表单
@@ -133,10 +125,16 @@ namespace Essensoft.AspNetCore.Payment.JDPay
 
         public async Task<T> ExecuteAsync<T>(IJDPayNPP10Request<T> request) where T : JDPayResponse
         {
+            return await ExecuteAsync(request, null);
+        }
+
+        public async Task<T> ExecuteAsync<T>(IJDPayNPP10Request<T> request, string optionsName) where T : JDPayResponse
+        {
+            var options = string.IsNullOrEmpty(optionsName) ? OptionsSnapshotAccessor.Value : OptionsSnapshotAccessor.Get(optionsName);
             var sortedTxtParams = new JDPayDictionary(request.GetParameters())
             {
-                { JDPayContants.CUSTOMER_NO, Options.CustomerNo },
-                { JDPayContants.SIGN_TYPE, Options.SignType }
+                { JDPayContants.CUSTOMER_NO, options.CustomerNo },
+                { JDPayContants.SIGN_TYPE, options.SignType }
             };
 
             var isEncrypt = false;
@@ -146,12 +144,12 @@ namespace Essensoft.AspNetCore.Payment.JDPay
                 isEncrypt = true;
             }
 
-            var encryptDic = JDPaySecurity.EncryptData(Options.PrivateCret, Options.Password, Options.PublicCert, sortedTxtParams, Options.SingKey, Options.EncryptType, isEncrypt);
+            var encryptDic = JDPaySecurity.EncryptData(options.PrivateCret, options.Password, options.PublicCert, sortedTxtParams, options.SingKey, options.EncryptType, isEncrypt);
 
             var content = JDPayUtility.BuildQuery(encryptDic);
             Logger?.LogTrace(0, "Request:{content}", content);
 
-            using (var client = ClientFactory.CreateClient(JDPayOptions.DefaultClientName))
+            using (var client = ClientFactory.CreateClient())
             {
                 var body = await HttpClientUtility.DoPostAsync(client, request.GetRequestUrl(), content, "application/x-www-form-urlencoded");
                 Logger?.LogTrace(1, "Response:{content}", body);
@@ -160,7 +158,7 @@ namespace Essensoft.AspNetCore.Payment.JDPay
 
                 // 验签
                 var dic = JsonConvert.DeserializeObject<JDPayDictionary>(body);
-                if (!JDPaySecurity.VerifySign(dic, Options.SingKey))
+                if (!JDPaySecurity.VerifySign(dic, options.SingKey))
                 {
                     throw new Exception("sign check fail: check Sign and Data Fail!");
                 }
@@ -174,41 +172,41 @@ namespace Essensoft.AspNetCore.Payment.JDPay
 
         #region Common Method
 
-        private string BuildEncryptXml<T>(IJDPayRequest<T> request, JDPayDictionary dic) where T : JDPayResponse
+        private string BuildEncryptXml<T>(IJDPayRequest<T> request, JDPayDictionary dic, JDPayOptions options) where T : JDPayResponse
         {
             var xmldoc = JDPayUtility.SortedDictionary2AllXml(dic);
             var smlStr = JDPayUtility.ConvertXmlToString(xmldoc);
             var sha256SourceSignString = SHA256.Compute(smlStr);
-            var encyptBytes = RSA_ECB_PKCS1Padding.Encrypt(Encoding.UTF8.GetBytes(sha256SourceSignString), Options.PrivateKey);
+            var encyptBytes = RSA_ECB_PKCS1Padding.Encrypt(Encoding.UTF8.GetBytes(sha256SourceSignString), options.PrivateKey);
             var sign = Convert.ToBase64String(encyptBytes, Base64FormattingOptions.InsertLineBreaks);
             var data = smlStr.Replace("</jdpay>", "<sign>" + sign + "</sign></jdpay>");
-            var encrypt = JDPaySecurity.EncryptECB(data, Options.DesKeyBase64);
+            var encrypt = JDPaySecurity.EncryptECB(data, options.DesKeyBase64);
             // 字典排序
             var reqdic = new JDPayDictionary
             {
                 { JDPayContants.VERSION, request.GetApiVersion() },
-                { JDPayContants.MERCHANT, Options.Merchant },
+                { JDPayContants.MERCHANT, options.Merchant },
                 { JDPayContants.ENCRYPT, Convert.ToBase64String(Encoding.UTF8.GetBytes(encrypt)) }
             };
 
             return JDPayUtility.SortedDictionary2XmlStr(reqdic);
         }
 
-        private JDPayDictionary BuildEncryptDic<T>(IJDPayRequest<T> request, IDictionary<string, string> parameters) where T : JDPayResponse
+        private JDPayDictionary BuildEncryptDic<T>(IJDPayRequest<T> request, IDictionary<string, string> parameters, JDPayOptions options) where T : JDPayResponse
         {
             var signDic = new JDPayDictionary(parameters)
             {
                 { JDPayContants.VERSION, request.GetApiVersion() },
-                { JDPayContants.MERCHANT, Options.Merchant },
+                { JDPayContants.MERCHANT, options.Merchant },
             };
 
             var signContent = JDPaySecurity.GetSignContent(signDic);
-            var sign = JDPaySecurity.RSASign(signContent, Options.PrivateKey);
+            var sign = JDPaySecurity.RSASign(signContent, options.PrivateKey);
 
             var encyptDic = new JDPayDictionary
             {
                 { JDPayContants.VERSION, request.GetApiVersion() },
-                { JDPayContants.MERCHANT, Options.Merchant },
+                { JDPayContants.MERCHANT, options.Merchant },
                 { JDPayContants.SIGN, sign }
             };
 
@@ -216,7 +214,7 @@ namespace Essensoft.AspNetCore.Payment.JDPay
             {
                 if (!string.IsNullOrEmpty(iter.Value))
                 {
-                    encyptDic.Add(iter.Key, JDPaySecurity.EncryptECB(iter.Value, Options.DesKeyBase64));
+                    encyptDic.Add(iter.Key, JDPaySecurity.EncryptECB(iter.Value, options.DesKeyBase64));
                 }
             }
             return encyptDic;

+ 21 - 35
src/Essensoft.AspNetCore.Payment.JDPay/JDPayNotifyClient.cs

@@ -17,36 +17,16 @@ namespace Essensoft.AspNetCore.Payment.JDPay
     {
         public virtual ILogger Logger { get; set; }
 
-        public JDPayOptions Options { get; protected set; }
+        public virtual IOptionsSnapshot<JDPayOptions> OptionsSnapshotAccessor { get; set; }
 
         #region JDPayNotifyClient Constructors
 
         public JDPayNotifyClient(
             ILogger<JDPayNotifyClient> logger,
-            IOptions<JDPayOptions> optionsAccessor)
+            IOptionsSnapshot<JDPayOptions> optionsAccessor)
         {
             Logger = logger;
-            Options = optionsAccessor.Value;
-
-            if (string.IsNullOrEmpty(Options.Merchant))
-            {
-                throw new ArgumentNullException(nameof(Options.Merchant));
-            }
-
-            if (string.IsNullOrEmpty(Options.RsaPrivateKey))
-            {
-                throw new ArgumentNullException(nameof(Options.RsaPrivateKey));
-            }
-
-            if (string.IsNullOrEmpty(Options.RsaPublicKey))
-            {
-                throw new ArgumentNullException(nameof(Options.RsaPublicKey));
-            }
-
-            if (string.IsNullOrEmpty(Options.DesKey))
-            {
-                throw new ArgumentNullException(nameof(Options.DesKey));
-            }          
+            OptionsSnapshotAccessor = optionsAccessor;
         }
 
         #endregion
@@ -55,11 +35,17 @@ namespace Essensoft.AspNetCore.Payment.JDPay
 
         public async Task<T> ExecuteAsync<T>(HttpRequest request) where T : JDPayNotifyResponse
         {
+            return await ExecuteAsync<T>(request, null);
+        }
+
+        public async Task<T> ExecuteAsync<T>(HttpRequest request, string optionsName) where T : JDPayNotifyResponse
+        {
+            var options = string.IsNullOrEmpty(optionsName) ? OptionsSnapshotAccessor.Value : OptionsSnapshotAccessor.Get(optionsName);
             if (request.HasFormContentType || request.Method == "GET")
             {
                 var rspInstance = Activator.CreateInstance<T>();
 
-                var parameters = GetParameters(request, !(rspInstance is JDPayDefrayPayNotifyResponse));
+                var parameters = GetParameters(request, options, !(rspInstance is JDPayDefrayPayNotifyResponse));
 
                 var query = JDPayUtility.BuildQuery(parameters);
                 Logger?.LogTrace(0, "Request:{query}", query);
@@ -69,11 +55,11 @@ namespace Essensoft.AspNetCore.Payment.JDPay
 
                 if (rsp is JDPayDefrayPayNotifyResponse)
                 {
-                    CheckNotifyDefrayPaySign(rsp.Parameters);
+                    CheckNotifyDefrayPaySign(rsp.Parameters, options);
                 }
                 else
                 {
-                    CheckNotifySign(rsp.Parameters);
+                    CheckNotifySign(rsp.Parameters, options);
                 }
 
                 return rsp;
@@ -89,7 +75,7 @@ namespace Essensoft.AspNetCore.Payment.JDPay
                 {
                     var encrypt = rsp.Encrypt;
                     var base64EncryptStr = Encoding.UTF8.GetString(Convert.FromBase64String(encrypt));
-                    var reqBody = JDPaySecurity.DecryptECB(base64EncryptStr,Options.DesKeyBase64);
+                    var reqBody = JDPaySecurity.DecryptECB(base64EncryptStr, options.DesKeyBase64);
                     Logger?.LogTrace(1, "Encrypt Content:{reqBody}", reqBody);
 
                     var reqBodyDoc = new XmlDocument() { XmlResolver = null };
@@ -107,7 +93,7 @@ namespace Essensoft.AspNetCore.Payment.JDPay
                         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), Options.PublicKey);
+                    var decryptByte = RSA_ECB_PKCS1Padding.Decrypt(Convert.FromBase64String(sign), options.PublicKey);
                     var decryptStr = JDPaySecurity.BytesToString(decryptByte);
                     if (sha256SourceSignString == decryptStr)
                     {
@@ -135,7 +121,7 @@ namespace Essensoft.AspNetCore.Payment.JDPay
 
         #region Common Method
 
-        private JDPayDictionary GetParameters(HttpRequest request, bool isDecrypt = true)
+        private JDPayDictionary GetParameters(HttpRequest request, JDPayOptions options, bool isDecrypt = true)
         {
             var parameters = new JDPayDictionary();
 
@@ -148,7 +134,7 @@ namespace Essensoft.AspNetCore.Payment.JDPay
                         var value = iter.Value.ToString();
                         if (isDecrypt)
                         {
-                            value = iter.Key == JDPayContants.SIGN ? iter.Value.ToString() : JDPaySecurity.DecryptECB(iter.Value, Options.DesKeyBase64);
+                            value = iter.Key == JDPayContants.SIGN ? iter.Value.ToString() : JDPaySecurity.DecryptECB(iter.Value, options.DesKeyBase64);
                         }
                         parameters.Add(iter.Key, value);
                     }
@@ -163,7 +149,7 @@ namespace Essensoft.AspNetCore.Payment.JDPay
                         var value = iter.Value.ToString();
                         if (isDecrypt)
                         {
-                            value = iter.Key == JDPayContants.SIGN ? iter.Value.ToString() : JDPaySecurity.DecryptECB(iter.Value, Options.DesKeyBase64);
+                            value = iter.Key == JDPayContants.SIGN ? iter.Value.ToString() : JDPaySecurity.DecryptECB(iter.Value, options.DesKeyBase64);
                         }
                         parameters.Add(iter.Key, value);
                     }
@@ -172,7 +158,7 @@ namespace Essensoft.AspNetCore.Payment.JDPay
             return parameters;
         }
 
-        private void CheckNotifySign(JDPayDictionary parameters)
+        private void CheckNotifySign(JDPayDictionary parameters, JDPayOptions options)
         {
             if (parameters.Count == 0)
             {
@@ -185,13 +171,13 @@ namespace Essensoft.AspNetCore.Payment.JDPay
             }
 
             var signContent = JDPaySecurity.GetSignContent(parameters);
-            if (!JDPaySecurity.RSACheckContent(signContent, sign, Options.PublicKey))
+            if (!JDPaySecurity.RSACheckContent(signContent, sign, options.PublicKey))
             {
                 throw new Exception("sign check fail: check Sign and Data Fail");
             }
         }
 
-        private void CheckNotifyDefrayPaySign(JDPayDictionary parameters)
+        private void CheckNotifyDefrayPaySign(JDPayDictionary parameters, JDPayOptions options)
         {
             if (parameters.Count == 0)
             {
@@ -203,7 +189,7 @@ namespace Essensoft.AspNetCore.Payment.JDPay
                 throw new Exception("sign check fail: sign is Empty!");
             }
 
-            if (!JDPaySecurity.VerifySign(parameters, Options.SingKey))
+            if (!JDPaySecurity.VerifySign(parameters, options.SingKey))
             {
                 throw new Exception("sign check fail: check Sign and Data Fail!");
             }

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

@@ -6,8 +6,6 @@ namespace Essensoft.AspNetCore.Payment.JDPay
 {
     public class JDPayOptions
     {
-        public const string DefaultClientName = "Payment.JDPay.Client";
-
         /// <summary>
         /// 商户号
         /// </summary>

+ 0 - 14
src/Essensoft.AspNetCore.Payment.JDPay/JDPayOptionsAccessor.cs

@@ -1,14 +0,0 @@
-using Microsoft.Extensions.Options;
-
-namespace Essensoft.AspNetCore.Payment.JDPay
-{
-    public class JDPayOptionsAccessor : IOptions<JDPayOptions>
-    {
-        public JDPayOptionsAccessor(JDPayOptions options)
-        {
-            Value = options;
-        }
-
-        public JDPayOptions Value { get; }
-    }
-}

+ 2 - 9
src/Essensoft.AspNetCore.Payment.JDPay/ServiceCollectionExtensions.cs

@@ -1,6 +1,5 @@
 using System;
 using Essensoft.AspNetCore.Payment.JDPay;
-using Essensoft.AspNetCore.Payment.JDPay.Utility;
 
 namespace Microsoft.Extensions.DependencyInjection
 {
@@ -16,18 +15,12 @@ namespace Microsoft.Extensions.DependencyInjection
             this IServiceCollection services,
             Action<JDPayOptions> setupAction)
         {
-            services.AddSingleton<JDPayClient>();
-            services.AddSingleton<JDPayNotifyClient>();
+            services.AddScoped<IJDPayClient, JDPayClient>();
+            services.AddScoped<IJDPayNotifyClient, JDPayNotifyClient>();
             if (setupAction != null)
             {
                 services.Configure(setupAction);
             }
         }
-
-        public static void AddJDPayHttpClient(
-            this IServiceCollection services)
-        {
-            services.AddHttpClient(JDPayOptions.DefaultClientName);
-        }
     }
 }

+ 3 - 3
src/Essensoft.AspNetCore.Payment.LianLianPay/Essensoft.AspNetCore.Payment.LianLianPay.csproj

@@ -5,9 +5,9 @@
     <Company>Essensoft</Company>
     <Authors>Roc</Authors>
     <Product>Payment</Product>
-    <Version>1.6.2</Version>
-    <AssemblyVersion>1.6.2.0</AssemblyVersion>
-    <FileVersion>1.6.2.0</FileVersion>
+    <Version>2.0.0</Version>
+    <AssemblyVersion>2.0.0.0</AssemblyVersion>
+    <FileVersion>2.0.0.0</FileVersion>
     <Description>Essensoft.AspNetCore.Payment.LianLianPay</Description>
     <Copyright>© Essensoft 2018</Copyright>
     <PackageProjectUrl>https://github.com/Essensoft/Payment</PackageProjectUrl>

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

@@ -2,13 +2,26 @@
 
 namespace Essensoft.AspNetCore.Payment.LianLianPay
 {
+    /// <summary>
+    /// LianLianPay客户端。
+    /// </summary>
     public interface ILianLianPayClient
     {
         /// <summary>
         /// 执行LianLianPay API请求。
         /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
         /// <param name="request">具体的LianLianPay API请求</param>
         /// <returns>领域对象</returns>
         Task<T> ExecuteAsync<T>(ILianLianPayRequest<T> request) where T : LianLianPayResponse;
+
+        /// <summary>
+        /// 执行LianLianPay API请求。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">具体的LianLianPay API请求</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteAsync<T>(ILianLianPayRequest<T> request, string optionsName) where T : LianLianPayResponse;
     }
 }

+ 18 - 0
src/Essensoft.AspNetCore.Payment.LianLianPay/ILianLianPayNotifyClient.cs

@@ -3,8 +3,26 @@ using Microsoft.AspNetCore.Http;
 
 namespace Essensoft.AspNetCore.Payment.LianLianPay
 {
+    /// <summary>
+    /// LianLianPay通知解析客户端。
+    /// </summary>
     public interface ILianLianPayNotifyClient
     {
+        /// <summary>
+        /// 执行LianLianPay通知请求解析。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">控制器的请求</param>
+        /// <returns>领域对象</returns>
         Task<T> ExecuteAsync<T>(HttpRequest request) where T : LianLianPayNotifyResponse;
+
+        /// <summary>
+        /// 执行LianLianPay通知请求解析。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">控制器的请求</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteAsync<T>(HttpRequest request, string optionsName) where T : LianLianPayNotifyResponse;
     }
 }

+ 20 - 34
src/Essensoft.AspNetCore.Payment.LianLianPay/LianLianPayClient.cs

@@ -25,38 +25,18 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
 
         public virtual IHttpClientFactory ClientFactory { get; set; }
 
-        public LianLianPayOptions Options { get; protected set; }
+        public virtual IOptionsSnapshot<LianLianPayOptions> OptionsSnapshotAccessor { get; set; }
 
         #region LianLianPayClient Constructors
 
         public LianLianPayClient(
             ILogger<LianLianPayClient> logger,
             IHttpClientFactory clientFactory,
-            IOptions<LianLianPayOptions> optionsAccessor)
+            IOptionsSnapshot<LianLianPayOptions> optionsAccessor)
         {
             Logger = logger;
             ClientFactory = clientFactory;
-            Options = optionsAccessor.Value;
-
-            if (string.IsNullOrEmpty(Options.OidPartner))
-            {
-                throw new ArgumentNullException(nameof(Options.OidPartner));
-            }
-
-            if (string.IsNullOrEmpty(Options.BusiPartner))
-            {
-                throw new ArgumentNullException(nameof(Options.BusiPartner));
-            }
-
-            if (string.IsNullOrEmpty(Options.RsaPrivateKey))
-            {
-                throw new ArgumentNullException(nameof(Options.RsaPrivateKey));
-            }
-
-            if (string.IsNullOrEmpty(Options.RsaPublicKey))
-            {
-                throw new ArgumentNullException(nameof(Options.RsaPublicKey));
-            }
+            OptionsSnapshotAccessor = optionsAccessor;
         }
 
         #endregion
@@ -65,29 +45,35 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
 
         public async Task<T> ExecuteAsync<T>(ILianLianPayRequest<T> request) where T : LianLianPayResponse
         {
+            return await ExecuteAsync(request, null);
+        }
+
+        public async Task<T> ExecuteAsync<T>(ILianLianPayRequest<T> request, string optionsName) where T : LianLianPayResponse
+        {
+            var options = string.IsNullOrEmpty(optionsName) ? OptionsSnapshotAccessor.Value : OptionsSnapshotAccessor.Get(optionsName);
             // 字典排序
             var txtParams = new LianLianPayDictionary(request.GetParameters())
             {
-                { OID_PARTNER, Options.OidPartner },
-                { SIGN_TYPE, Options.SignType },
+                { OID_PARTNER, options.OidPartner },
+                { SIGN_TYPE, options.SignType },
             };
 
-            if (request is LianLianPayCreateBillRequest|| request is LianLianPayUnifiedCardBindRequest)
+            if (request is LianLianPayCreateBillRequest || request is LianLianPayUnifiedCardBindRequest)
             {
                 txtParams.Add(TIME_STAMP, DateTime.Now);
-                txtParams.Add(BUSI_PARTNER, Options.BusiPartner);
+                txtParams.Add(BUSI_PARTNER, options.BusiPartner);
             }
 
             // 添加签名
             var signContent = LianLianPaySecurity.GetSignContent(txtParams);
-            txtParams.Add(SIGN, MD5WithRSA.SignData(signContent, Options.PrivateKey));
+            txtParams.Add(SIGN, MD5WithRSA.SignData(signContent, options.PrivateKey));
 
             var content = string.Empty;
             if (request is LianLianPayPaymentRequest || request is LianLianPayConfirmPaymentRequest)
             {
                 var plaintext = Serialize(txtParams);
-                var ciphertext = LianLianPaySecurity.Encrypt(plaintext, Options.PublicKey);
-                content = $"{{\"pay_load\":\"{ciphertext}\",\"oid_partner\":\"{Options.OidPartner}\"}}";
+                var ciphertext = LianLianPaySecurity.Encrypt(plaintext, options.PublicKey);
+                content = $"{{\"pay_load\":\"{ciphertext}\",\"oid_partner\":\"{options.OidPartner}\"}}";
             }
             else
             {
@@ -95,7 +81,7 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
             }
             Logger?.LogTrace(0, "Request:{content}", content);
 
-            using (var client = ClientFactory.CreateClient(LianLianPayOptions.DefaultClientName))
+            using (var client = ClientFactory.CreateClient())
             {
                 var body = await HttpClientUtility.DoPostAsync(client, request.GetRequestUrl(), content);
                 Logger?.LogTrace(1, "Response:{body}", body);
@@ -115,7 +101,7 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
                     excludePara.Add("agreement_list");
                 }
 
-                CheckNotifySign(rsp.Parameters, excludePara);
+                CheckNotifySign(rsp.Parameters, excludePara, options);
                 return rsp;
             }
         }
@@ -129,7 +115,7 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
             return JsonConvert.SerializeObject(value, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
         }
 
-        private void CheckNotifySign(LianLianPayDictionary parameters, List<string> excludePara)
+        private void CheckNotifySign(LianLianPayDictionary parameters, List<string> excludePara, LianLianPayOptions options)
         {
             if (parameters.Count == 0)
             {
@@ -139,7 +125,7 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
             if (parameters.TryGetValue("sign", out var sign))
             {
                 var prestr = LianLianPaySecurity.GetSignContent(parameters, excludePara);
-                if (!MD5WithRSA.VerifyData(prestr, sign, Options.PublicKey))
+                if (!MD5WithRSA.VerifyData(prestr, sign, options.PublicKey))
                 {
                     throw new Exception("sign check fail: check Sign and Data Fail JSON also");
                 }

+ 17 - 26
src/Essensoft.AspNetCore.Payment.LianLianPay/LianLianPayNotifyClient.cs

@@ -1,12 +1,12 @@
-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 System;
-using System.IO;
-using System.Threading.Tasks;
 
 namespace Essensoft.AspNetCore.Payment.LianLianPay
 {
@@ -14,31 +14,16 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
     {
         public virtual ILogger Logger { get; set; }
 
-        public LianLianPayOptions Options { get; protected set; }
+        public virtual IOptionsSnapshot<LianLianPayOptions> OptionsSnapshotAccessor { get; set; }
 
         #region LianLianPayNotifyClient Constructors
 
         public LianLianPayNotifyClient(
             ILogger<LianLianPayClient> logger,
-            IOptions<LianLianPayOptions> optionsAccessor)
+            IOptionsSnapshot<LianLianPayOptions> optionsAccessor)
         {
             Logger = logger;
-            Options = optionsAccessor?.Value;
-
-            if (string.IsNullOrEmpty(Options.OidPartner))
-            {
-                throw new ArgumentNullException(nameof(Options.OidPartner));
-            }
-
-            if (string.IsNullOrEmpty(Options.BusiPartner))
-            {
-                throw new ArgumentNullException(nameof(Options.BusiPartner));
-            }
-
-            if (string.IsNullOrEmpty(Options.RsaPublicKey))
-            {
-                throw new ArgumentNullException(nameof(Options.RsaPublicKey));
-            }          
+            OptionsSnapshotAccessor = optionsAccessor;
         }
 
         #endregion
@@ -47,6 +32,12 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
 
         public async Task<T> ExecuteAsync<T>(HttpRequest request) where T : LianLianPayNotifyResponse
         {
+            return await ExecuteAsync<T>(request, null);
+        }
+
+        public async Task<T> ExecuteAsync<T>(HttpRequest request, string optionsName) where T : LianLianPayNotifyResponse
+        {
+            var options = string.IsNullOrEmpty(optionsName) ? OptionsSnapshotAccessor.Value : OptionsSnapshotAccessor.Get(optionsName);
             if (request.HasFormContentType)
             {
                 var parameters = await GetParametersAsync(request);
@@ -55,7 +46,7 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
 
                 var parser = new LianLianPayDictionaryParser<T>();
                 var rsp = parser.Parse(parameters);
-                CheckNotifySign(parameters);
+                CheckNotifySign(parameters, options);
                 return rsp;
             }
             else if (request.HasTextJsonContentType())
@@ -65,7 +56,7 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
 
                 var parser = new LianLianPayJsonParser<T>();
                 var rsp = parser.Parse(body);
-                CheckNotifySign(rsp.Parameters);
+                CheckNotifySign(rsp.Parameters, options);
                 return rsp;
             }
             else
@@ -89,7 +80,7 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
             return parameters;
         }
 
-        private void CheckNotifySign(LianLianPayDictionary para)
+        private void CheckNotifySign(LianLianPayDictionary para, LianLianPayOptions options)
         {
             if (para.Count == 0)
             {
@@ -102,7 +93,7 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
             }
 
             var prestr = LianLianPaySecurity.GetSignContent(para);
-            if (!MD5WithRSA.VerifyData(prestr, sign, Options.PublicKey))
+            if (!MD5WithRSA.VerifyData(prestr, sign, options.PublicKey))
             {
                 throw new Exception("sign check fail: check Sign and Data Fail JSON also");
             }

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

@@ -5,8 +5,6 @@ namespace Essensoft.AspNetCore.Payment.LianLianPay
 {
     public class LianLianPayOptions
     {
-        public const string DefaultClientName = "Payment.LianLianPay.Client";
-
         /// <summary>
         /// 连连支付公钥
         /// </summary>

+ 0 - 14
src/Essensoft.AspNetCore.Payment.LianLianPay/LianLianPayOptionsAccessor.cs

@@ -1,14 +0,0 @@
-using Microsoft.Extensions.Options;
-
-namespace Essensoft.AspNetCore.Payment.LianLianPay
-{
-    public class LianLianPayOptionsAccessor : IOptions<LianLianPayOptions>
-    {
-        public LianLianPayOptionsAccessor(LianLianPayOptions options)
-        {
-            Value = options;
-        }
-
-        public LianLianPayOptions Value { get; }
-    }
-}

+ 2 - 9
src/Essensoft.AspNetCore.Payment.LianLianPay/ServiceCollectionExtensions.cs

@@ -1,6 +1,5 @@
 using System;
 using Essensoft.AspNetCore.Payment.LianLianPay;
-using Essensoft.AspNetCore.Payment.LianLianPay.Utility;
 
 namespace Microsoft.Extensions.DependencyInjection
 {
@@ -16,18 +15,12 @@ namespace Microsoft.Extensions.DependencyInjection
             this IServiceCollection services,
             Action<LianLianPayOptions> setupAction)
         {
-            services.AddSingleton<LianLianPayClient>();
-            services.AddSingleton<LianLianPayNotifyClient>();
+            services.AddScoped<ILianLianPayClient, LianLianPayClient>();
+            services.AddScoped<ILianLianPayNotifyClient, LianLianPayNotifyClient>();
             if (setupAction != null)
             {
                 services.Configure(setupAction);
             }
         }
-
-        public static void AddLianLianPayHttpClient(
-            this IServiceCollection services)
-        {
-            services.AddHttpClient(LianLianPayOptions.DefaultClientName);
-        }
     }
 }

+ 3 - 3
src/Essensoft.AspNetCore.Payment.QPay/Essensoft.AspNetCore.Payment.QPay.csproj

@@ -5,9 +5,9 @@
     <Company>Essensoft</Company>
     <Authors>Roc</Authors>
     <Product>Payment</Product>
-    <Version>1.6.2</Version>
-    <AssemblyVersion>1.6.2.0</AssemblyVersion>
-    <FileVersion>1.6.2.0</FileVersion>
+    <Version>2.0.0</Version>
+    <AssemblyVersion>2.0.0.0</AssemblyVersion>
+    <FileVersion>2.0.0.0</FileVersion>
     <Description>Essensoft.AspNetCore.Payment.QPay</Description>
     <Copyright>© Essensoft 2018</Copyright>
     <PackageProjectUrl>https://github.com/Essensoft/Payment</PackageProjectUrl>

+ 27 - 3
src/Essensoft.AspNetCore.Payment.QPay/IQPayClient.cs

@@ -2,21 +2,45 @@
 
 namespace Essensoft.AspNetCore.Payment.QPay
 {
+    /// <summary>
+    /// QPay客户端。
+    /// </summary>
     public interface IQPayClient
     {
         /// <summary>
         /// 执行QPay API请求。
         /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
         /// <param name="request">具体的QPay API请求</param>
         /// <returns>领域对象</returns>
         Task<T> ExecuteAsync<T>(IQPayRequest<T> request) where T : QPayResponse;
 
         /// <summary>
-        /// 执行QPay Certificate API请求。
+        /// 执行QPay API请求。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">具体的QPay API请求</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteAsync<T>(IQPayRequest<T> request, string optionsName) where T : QPayResponse;
+
+        /// <summary>
+        /// 执行QPay API证书请求。
         /// </summary>
-        /// <param name="request">具体的QPay Certificate API请求</param>
-        /// <param name="certificateName">请求所使用证书的名称</param>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">具体的QPay API证书请求</param>
+        /// <param name="certificateName">证书名称</param>
         /// <returns>领域对象</returns>
         Task<T> ExecuteAsync<T>(IQPayCertificateRequest<T> request, string certificateName) where T : QPayResponse;
+
+        /// <summary>
+        /// 执行QPay API证书请求。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">具体的QPay API证书请求</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <param name="certificateName">证书名称</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteAsync<T>(IQPayCertificateRequest<T> request, string optionsName, string certificateName) where T : QPayResponse;
     }
 }

+ 18 - 0
src/Essensoft.AspNetCore.Payment.QPay/IQPayNotifyClient.cs

@@ -3,8 +3,26 @@ using Microsoft.AspNetCore.Http;
 
 namespace Essensoft.AspNetCore.Payment.QPay
 {
+    /// <summary>
+    /// QPay通知解析客户端。
+    /// </summary>
     public interface IQPayNotifyClient
     {
+        /// <summary>
+        /// 执行QPay通知请求解析。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">控制器的请求</param>
+        /// <returns>领域对象</returns>
         Task<T> ExecuteAsync<T>(HttpRequest request) where T : QPayNotifyResponse;
+
+        /// <summary>
+        /// 执行QPay通知请求解析。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">控制器的请求</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteAsync<T>(HttpRequest request, string optionsName) where T : QPayNotifyResponse;
     }
 }

+ 28 - 27
src/Essensoft.AspNetCore.Payment.QPay/QPayClient.cs

@@ -19,28 +19,18 @@ namespace Essensoft.AspNetCore.Payment.QPay
 
         public virtual IHttpClientFactory ClientFactory { get; set; }
 
-        public QPayOptions Options { get; protected set; }
+        public virtual IOptionsSnapshot<QPayOptions> OptionsSnapshotAccessor { get; set; }
 
         #region QPayClient Constructors
 
         public QPayClient(
             ILogger<QPayClient> logger,
             IHttpClientFactory clientFactory,
-            IOptions<QPayOptions> optionsAccessor)
+            IOptionsSnapshot<QPayOptions> optionsAccessor)
         {
             Logger = logger;
             ClientFactory = clientFactory;
-            Options = optionsAccessor.Value;
-
-            if (string.IsNullOrEmpty(Options.MchId))
-            {
-                throw new ArgumentNullException(nameof(Options.MchId));
-            }
-
-            if (string.IsNullOrEmpty(Options.Key))
-            {
-                throw new ArgumentNullException(nameof(Options.Key));
-            }
+            OptionsSnapshotAccessor = optionsAccessor;
         }
 
         #endregion
@@ -49,31 +39,37 @@ namespace Essensoft.AspNetCore.Payment.QPay
 
         public async Task<T> ExecuteAsync<T>(IQPayRequest<T> request) where T : QPayResponse
         {
+            return await ExecuteAsync(request, null);
+        }
+
+        public async Task<T> ExecuteAsync<T>(IQPayRequest<T> request, string optionsName) where T : QPayResponse
+        {
+            var options = string.IsNullOrEmpty(optionsName) ? OptionsSnapshotAccessor.Value : OptionsSnapshotAccessor.Get(optionsName);
             // 字典排序
             var sortedTxtParams = new QPayDictionary(request.GetParameters())
             {
-                { MCHID, Options.MchId },
+                { MCHID, options.MchId },
                 { NONCE_STR, Guid.NewGuid().ToString("N") }
             };
 
             if (string.IsNullOrEmpty(sortedTxtParams.GetValue(APPID)))
             {
-                sortedTxtParams.Add(APPID, Options.AppId);
+                sortedTxtParams.Add(APPID, options.AppId);
             }
 
-            sortedTxtParams.Add(SIGN, QPaySignature.SignWithKey(sortedTxtParams, Options.Key));
+            sortedTxtParams.Add(SIGN, QPaySignature.SignWithKey(sortedTxtParams, options.Key));
 
             var content = QPayUtility.BuildContent(sortedTxtParams);
             Logger?.LogTrace(0, "Request:{content}", content);
 
-            using (var client = ClientFactory.CreateClient(QPayOptions.DefaultClientName))
+            using (var client = ClientFactory.CreateClient())
             {
                 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);
+                CheckResponseSign(rsp, options);
                 return rsp;
             }
         }
@@ -82,32 +78,37 @@ namespace Essensoft.AspNetCore.Payment.QPay
 
         #region IQPayClient Members
 
-        public async Task<T> ExecuteAsync<T>(IQPayCertificateRequest<T> request, string certificateName = "Default") where T : QPayResponse
+        public async Task<T> ExecuteAsync<T>(IQPayCertificateRequest<T> request, string certificateName) where T : QPayResponse
+        {
+            return await ExecuteAsync(request, null, certificateName);
+        }
+
+        public async Task<T> ExecuteAsync<T>(IQPayCertificateRequest<T> request, string optionsName, string certificateName) where T : QPayResponse
         {
+            var options = string.IsNullOrEmpty(optionsName) ? OptionsSnapshotAccessor.Value : OptionsSnapshotAccessor.Get(optionsName);
             // 字典排序
             var sortedTxtParams = new QPayDictionary(request.GetParameters())
             {
-                { MCHID, Options.MchId },
+                { MCHID, options.MchId },
                 { NONCE_STR, Guid.NewGuid().ToString("N") }
             };
 
             if (string.IsNullOrEmpty(sortedTxtParams.GetValue(APPID)))
             {
-                sortedTxtParams.Add(APPID, Options.AppId);
+                sortedTxtParams.Add(APPID, options.AppId);
             }
 
-            sortedTxtParams.Add(SIGN, QPaySignature.SignWithKey(sortedTxtParams, Options.Key));
+            sortedTxtParams.Add(SIGN, QPaySignature.SignWithKey(sortedTxtParams, options.Key));
             var content = QPayUtility.BuildContent(sortedTxtParams);
             Logger?.LogTrace(0, "Request:{content}", content);
-
-            using (var client = ClientFactory.CreateClient(QPayOptions.CertificateClientName + "." + certificateName))
+            using (var client = string.IsNullOrEmpty(certificateName) ? ClientFactory.CreateClient() : ClientFactory.CreateClient(certificateName))
             {
                 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);
+                CheckResponseSign(rsp, options);
                 return rsp;
             }
         }
@@ -116,7 +117,7 @@ namespace Essensoft.AspNetCore.Payment.QPay
 
         #region Common Method
 
-        private void CheckResponseSign(QPayResponse response)
+        private void CheckResponseSign(QPayResponse response, QPayOptions options)
         {
             if (string.IsNullOrEmpty(response.Body) || response?.Parameters == null)
             {
@@ -127,7 +128,7 @@ namespace Essensoft.AspNetCore.Payment.QPay
             {
                 if (response.Parameters["return_code"] == "SUCCESS" && !string.IsNullOrEmpty(sign))
                 {
-                    var cal_sign = QPaySignature.SignWithKey(response.Parameters, Options.Key);
+                    var cal_sign = QPaySignature.SignWithKey(response.Parameters, options.Key);
                     if (cal_sign != sign)
                     {
                         throw new Exception("sign check fail: check Sign and Data Fail!");

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

@@ -14,21 +14,16 @@ namespace Essensoft.AspNetCore.Payment.QPay
     {
         public virtual ILogger Logger { get; set; }
 
-        public QPayOptions Options { get; protected set; }
+        public virtual IOptionsSnapshot<QPayOptions> OptionsSnapshotAccessor { get; set; }
 
         #region QPayNotifyClient Constructors
 
         public QPayNotifyClient(
             ILogger<QPayNotifyClient> logger,
-            IOptions<QPayOptions> optionsAccessor)
+            IOptionsSnapshot<QPayOptions> optionsAccessor)
         {
             Logger = logger;
-            Options = optionsAccessor.Value;
-
-            if (string.IsNullOrEmpty(Options.Key))
-            {
-                throw new ArgumentNullException(nameof(Options.Key));
-            }
+            OptionsSnapshotAccessor = optionsAccessor;
         }
 
         #endregion
@@ -37,12 +32,18 @@ namespace Essensoft.AspNetCore.Payment.QPay
 
         public async Task<T> ExecuteAsync<T>(HttpRequest request) where T : QPayNotifyResponse
         {
+            return await ExecuteAsync<T>(request, null);
+        }
+
+        public async Task<T> ExecuteAsync<T>(HttpRequest request, string optionsName) where T : QPayNotifyResponse
+        {
+            var options = string.IsNullOrEmpty(optionsName) ? OptionsSnapshotAccessor.Value : OptionsSnapshotAccessor.Get(optionsName);
             var body = await new StreamReader(request.Body, Encoding.UTF8).ReadToEndAsync();
             Logger?.LogTrace(0, "Request:{body}", body);
 
             var parser = new QPayXmlParser<T>();
             var rsp = parser.Parse(body);
-            CheckNotifySign(rsp);
+            CheckNotifySign(rsp, options);
             return rsp;
         }
 
@@ -50,7 +51,7 @@ namespace Essensoft.AspNetCore.Payment.QPay
 
         #region Common Method
 
-        private void CheckNotifySign(QPayNotifyResponse response)
+        private void CheckNotifySign(QPayNotifyResponse response, QPayOptions options)
         {
             if (response?.Parameters?.Count == 0)
             {
@@ -62,7 +63,7 @@ namespace Essensoft.AspNetCore.Payment.QPay
                 throw new Exception("sign check fail: sign is Empty!");
             }
 
-            var cal_sign = QPaySignature.SignWithKey(response.Parameters, Options.Key);
+            var cal_sign = QPaySignature.SignWithKey(response.Parameters, options.Key);
             if (cal_sign != sign)
             {
                 throw new Exception("sign check fail: check Sign and Data Fail!");

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

@@ -2,9 +2,6 @@
 {
     public class QPayOptions
     {
-        public const string DefaultClientName = "Payment.QPay.Client";
-        public const string CertificateClientName = "Payment.QPay.CertificateClient";
-
         /// <summary>
         /// 应用ID
         /// </summary>

+ 0 - 14
src/Essensoft.AspNetCore.Payment.QPay/QPayOptionsAccessor.cs

@@ -1,14 +0,0 @@
-using Microsoft.Extensions.Options;
-
-namespace Essensoft.AspNetCore.Payment.QPay
-{
-    public class QPayOptionsAccessor : IOptions<QPayOptions>
-    {
-        public QPayOptionsAccessor(QPayOptions options)
-        {
-            Value = options;
-        }
-
-        public QPayOptions Value { get; }
-    }
-}

+ 2 - 29
src/Essensoft.AspNetCore.Payment.QPay/ServiceCollectionExtensions.cs

@@ -17,39 +17,12 @@ namespace Microsoft.Extensions.DependencyInjection
             this IServiceCollection services,
             Action<QPayOptions> setupAction)
         {
-            services.AddSingleton<QPayClient>();
-            services.AddSingleton<QPayNotifyClient>();
-
+            services.AddScoped<IQPayClient, QPayClient>();
+            services.AddScoped<IQPayNotifyClient, QPayNotifyClient>();
             if (setupAction != null)
             {
                 services.Configure(setupAction);
             }
         }
-
-        public static void AddQPayHttpClient(
-            this IServiceCollection services)
-        {
-            services.AddHttpClient(QPayOptions.DefaultClientName);
-        }
-
-        public static void AddQPayCertificateHttpClient(
-            this IServiceCollection services,
-            X509Certificate2 certificate)
-        {
-            services.AddQPayCertificateHttpClient(certificateName: "Default", certificate: certificate);
-        }
-
-        public static void AddQPayCertificateHttpClient(
-            this IServiceCollection services,
-            string certificateName,
-            X509Certificate2 certificate)
-        {
-            services.AddHttpClient(QPayOptions.CertificateClientName + "." + certificateName).ConfigurePrimaryHttpMessageHandler(() =>
-            {
-                var handler = new HttpClientHandler();
-                handler.ClientCertificates.Add(certificate);
-                return handler;
-            });
-        }
     }
 }

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

@@ -5,9 +5,9 @@
     <Company>Essensoft</Company>
     <Authors>Roc</Authors>
     <Product>Payment</Product>
-    <Version>1.6.2</Version>
-    <AssemblyVersion>1.6.2.0</AssemblyVersion>
-    <FileVersion>1.6.2.0</FileVersion>
+    <Version>2.0.0</Version>
+    <AssemblyVersion>2.0.0.0</AssemblyVersion>
+    <FileVersion>2.0.0.0</FileVersion>
     <Description>Essensoft.AspNetCore.Payment.Security</Description>
     <Copyright>© Essensoft 2018</Copyright>
     <PackageProjectUrl>https://github.com/Essensoft/Payment</PackageProjectUrl>

+ 3 - 3
src/Essensoft.AspNetCore.Payment.UnionPay/Essensoft.AspNetCore.Payment.UnionPay.csproj

@@ -5,9 +5,9 @@
     <Company>Essensoft</Company>
     <Authors>Roc</Authors>
     <Product>Payment</Product>
-    <Version>1.6.2</Version>
-    <AssemblyVersion>1.6.2.0</AssemblyVersion>
-    <FileVersion>1.6.2.0</FileVersion>
+    <Version>2.0.0</Version>
+    <AssemblyVersion>2.0.0.0</AssemblyVersion>
+    <FileVersion>2.0.0.0</FileVersion>
     <Description>Essensoft.AspNetCore.Payment.UnionPay</Description>
     <Copyright>© Essensoft 2018</Copyright>
     <PackageProjectUrl>https://github.com/Essensoft/Payment</PackageProjectUrl>

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

@@ -2,11 +2,15 @@
 
 namespace Essensoft.AspNetCore.Payment.UnionPay
 {
+    /// <summary>
+    /// UnionPay客户端。
+    /// </summary>
     public interface IUnionPayClient
     {
         /// <summary>
         /// 执行UnionPay API请求。
         /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
         /// <param name="request">具体的UnionPay API请求</param>
         /// <returns>领域对象</returns>
         Task<T> ExecuteAsync<T>(IUnionPayRequest<T> request) where T : UnionPayResponse;
@@ -14,6 +18,16 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
         /// <summary>
         /// 执行UnionPay API请求。
         /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">具体的UnionPay API请求</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteAsync<T>(IUnionPayRequest<T> request, string optionsName) where T : UnionPayResponse;
+
+        /// <summary>
+        /// 执行UnionPay API请求。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
         /// <param name="request">具体的UnionPay API请求</param>
         /// <returns>领域对象</returns>
         Task<T> PageExecuteAsync<T>(IUnionPayRequest<T> request) where T : UnionPayResponse;
@@ -21,8 +35,20 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
         /// <summary>
         /// 执行UnionPay API请求。
         /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
         /// <param name="request">具体的UnionPay API请求</param>
+        /// <param name="reqMethod">请求类型(POST/GET)</param>
         /// <returns>领域对象</returns>
         Task<T> PageExecuteAsync<T>(IUnionPayRequest<T> request, string reqMethod) where T : UnionPayResponse;
+
+        /// <summary>
+        /// 执行UnionPay API请求。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">具体的UnionPay API请求</param>
+        /// <param name="reqMethod">请求类型(POST/GET)</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <returns>领域对象</returns>
+        Task<T> PageExecuteAsync<T>(IUnionPayRequest<T> request, string reqMethod, string optionsName) where T : UnionPayResponse;
     }
 }

+ 18 - 0
src/Essensoft.AspNetCore.Payment.UnionPay/IUnionPayNotifyClient.cs

@@ -3,8 +3,26 @@ using Microsoft.AspNetCore.Http;
 
 namespace Essensoft.AspNetCore.Payment.UnionPay
 {
+    /// <summary>
+    /// UnionPay通知解析客户端。
+    /// </summary>
     public interface IUnionPayNotifyClient
     {
+        /// <summary>
+        /// 执行UnionPay通知请求解析。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">控制器的请求</param>
+        /// <returns>领域对象</returns>
         Task<T> ExecuteAsync<T>(HttpRequest request) where T : UnionPayNotifyResponse;
+
+        /// <summary>
+        /// 执行UnionPay通知请求解析。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">控制器的请求</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteAsync<T>(HttpRequest request, string optionsName) where T : UnionPayNotifyResponse;
     }
 }

+ 2 - 9
src/Essensoft.AspNetCore.Payment.UnionPay/ServiceCollectionExtensions.cs

@@ -1,6 +1,5 @@
 using System;
 using Essensoft.AspNetCore.Payment.UnionPay;
-using Essensoft.AspNetCore.Payment.UnionPay.Utility;
 
 namespace Microsoft.Extensions.DependencyInjection
 {
@@ -16,18 +15,12 @@ namespace Microsoft.Extensions.DependencyInjection
             this IServiceCollection services,
             Action<UnionPayOptions> setupAction)
         {
-            services.AddSingleton<UnionPayClient>();
-            services.AddSingleton<UnionPayNotifyClient>();
+            services.AddScoped<IUnionPayClient, UnionPayClient>();
+            services.AddScoped<IUnionPayNotifyClient, UnionPayNotifyClient>();
             if (setupAction != null)
             {
                 services.Configure(setupAction);
             }
         }
-
-        public static void AddUnionPayHttpClient(
-            this IServiceCollection services)
-        {
-            services.AddHttpClient(UnionPayOptions.DefaultClientName);
-        }
     }
 }

+ 34 - 66
src/Essensoft.AspNetCore.Payment.UnionPay/UnionPayClient.cs

@@ -24,43 +24,18 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
 
         public virtual IHttpClientFactory ClientFactory { get; set; }
 
-        public UnionPayOptions Options { get; protected set; }
+        public virtual IOptionsSnapshot<UnionPayOptions> OptionsSnapshotAccessor { get; set; }
 
         #region UnionPayClient Constructors
 
         public UnionPayClient(
             ILogger<UnionPayClient> logger,
             IHttpClientFactory clientFactory,
-            IOptions<UnionPayOptions> optionsAccessor)
+            IOptionsSnapshot<UnionPayOptions> optionsAccessor)
         {
             Logger = logger;
             ClientFactory = clientFactory;
-            Options = optionsAccessor.Value;
-
-            if (string.IsNullOrEmpty(Options.SignCert))
-            {
-                throw new ArgumentNullException(nameof(Options.SignCert));
-            }
-
-            if (string.IsNullOrEmpty(Options.SignCertPassword))
-            {
-                throw new ArgumentNullException(nameof(Options.SignCertPassword));
-            }
-
-            if (string.IsNullOrEmpty(Options.EncryptCert))
-            {
-                throw new ArgumentNullException(nameof(Options.EncryptCert));
-            }
-
-            if (string.IsNullOrEmpty(Options.MiddleCert))
-            {
-                throw new ArgumentNullException(nameof(Options.MiddleCert));
-            }
-
-            if (string.IsNullOrEmpty(Options.RootCert))
-            {
-                throw new ArgumentNullException(nameof(Options.RootCert));
-            }          
+            OptionsSnapshotAccessor = optionsAccessor;
         }
 
         #endregion
@@ -69,19 +44,16 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
 
         public async Task<T> ExecuteAsync<T>(IUnionPayRequest<T> request) where T : UnionPayResponse
         {
-            var version = string.Empty;
+            return await ExecuteAsync(request, null);
+        }
 
-            if (!string.IsNullOrEmpty(request.GetApiVersion()))
-            {
-                version = request.GetApiVersion();
-            }
-            else
-            {
-                version = Options.Version;
-            }
+        public async Task<T> ExecuteAsync<T>(IUnionPayRequest<T> request, string optionsName) where T : UnionPayResponse
+        {
+            var options = string.IsNullOrEmpty(optionsName) ? OptionsSnapshotAccessor.Value : OptionsSnapshotAccessor.Get(optionsName);
+            var version = string.IsNullOrEmpty(request.GetApiVersion()) ? options.Version : request.GetApiVersion();
 
-            var merId = Options.MerId;
-            if (Options.TestMode && (request is UnionPayForm05_7_FileTransferRequest || request is UnionPayForm_6_6_FileTransferRequest))
+            var merId = options.MerId;
+            if (options.TestMode && (request is UnionPayForm05_7_FileTransferRequest || request is UnionPayForm_6_6_FileTransferRequest))
             {
                 merId = "700000000000001";
             }
@@ -89,25 +61,25 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
             var txtParams = new UnionPayDictionary(request.GetParameters())
             {
                 { VERSION, version },
-                { ENCODING, Options.Encoding },
-                { SIGNMETHOD, Options.SignMethod },
-                { ACCESSTYPE, Options.AccessType },
+                { ENCODING, options.Encoding },
+                { SIGNMETHOD, options.SignMethod },
+                { ACCESSTYPE, options.AccessType },
                 { MERID, merId },
             };
 
             if (request.HasEncryptCertId())
             {
-                txtParams.Add(ENCRYPTCERTID, Options.EncryptCertificate.certId);
+                txtParams.Add(ENCRYPTCERTID, options.EncryptCertificate.certId);
             }
 
-            UnionPaySignature.Sign(txtParams, Options.SignCertificate.certId, Options.SignCertificate.key, Options.SecureKey);
+            UnionPaySignature.Sign(txtParams, options.SignCertificate.certId, options.SignCertificate.key, options.SecureKey);
 
             var query = UnionPayUtility.BuildQuery(txtParams);
             Logger?.LogTrace(0, "Request:{query}", query);
 
-            using (var client = ClientFactory.CreateClient(UnionPayOptions.DefaultClientName))
+            using (var client = ClientFactory.CreateClient())
             {
-                var body = await HttpClientUtility.DoPostAsync(client, request.GetRequestUrl(Options.TestMode), query);
+                var body = await HttpClientUtility.DoPostAsync(client, request.GetRequestUrl(options.TestMode), query);
                 Logger?.LogTrace(1, "Response:{content}", body);
 
                 var dic = ParseQueryString(body);
@@ -117,8 +89,8 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
                     throw new Exception("sign check fail: Body is Empty!");
                 }
 
-                var ifValidateCNName = !Options.TestMode;
-                if (!UnionPaySignature.Validate(dic, Options.RootCertificate.cert, Options.MiddleCertificate.cert, Options.SecureKey, ifValidateCNName))
+                var ifValidateCNName = !options.TestMode;
+                if (!UnionPaySignature.Validate(dic, options.RootCertificate.cert, options.MiddleCertificate.cert, options.SecureKey, ifValidateCNName))
                 {
                     throw new Exception("sign check fail: check Sign and Data Fail!");
                 }
@@ -136,36 +108,32 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
 
         public Task<T> PageExecuteAsync<T>(IUnionPayRequest<T> request) where T : UnionPayResponse
         {
-            return PageExecuteAsync(request, "POST");
+            return PageExecuteAsync(request, null, "POST");
         }
 
-        public Task<T> PageExecuteAsync<T>(IUnionPayRequest<T> request, string reqMethod) where T : UnionPayResponse
+        public Task<T> PageExecuteAsync<T>(IUnionPayRequest<T> request, string optionsName) where T : UnionPayResponse
         {
-            var version = string.Empty;
-
-            if (!string.IsNullOrEmpty(request.GetApiVersion()))
-            {
-                version = request.GetApiVersion();
-            }
-            else
-            {
-                version = Options.Version;
-            }
+            return PageExecuteAsync(request, optionsName, "POST");
+        }
 
+        public Task<T> PageExecuteAsync<T>(IUnionPayRequest<T> request, string optionsName, string reqMethod) where T : UnionPayResponse
+        {
+            var options = string.IsNullOrEmpty(optionsName) ? OptionsSnapshotAccessor.Value : OptionsSnapshotAccessor.Get(optionsName);
+            var version = string.IsNullOrEmpty(request.GetApiVersion()) ? options.Version : request.GetApiVersion();
             var txtParams = new UnionPayDictionary(request.GetParameters())
             {
                 { VERSION, version },
-                { ENCODING, Options.Encoding },
-                { SIGNMETHOD, Options.SignMethod },
-                { ACCESSTYPE, Options.AccessType },
-                { MERID, Options.MerId },
+                { ENCODING, options.Encoding },
+                { SIGNMETHOD, options.SignMethod },
+                { ACCESSTYPE, options.AccessType },
+                { MERID, options.MerId },
             };
 
-            UnionPaySignature.Sign(txtParams, Options.SignCertificate.certId, Options.SignCertificate.key, Options.SecureKey);
+            UnionPaySignature.Sign(txtParams, options.SignCertificate.certId, options.SignCertificate.key, options.SecureKey);
 
             var rsp = Activator.CreateInstance<T>();
 
-            var url = request.GetRequestUrl(Options.TestMode);
+            var url = request.GetRequestUrl(options.TestMode);
             if (reqMethod.ToUpper() == "GET")
             {
                 //拼接get请求的url

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

@@ -12,26 +12,16 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
     {
         public virtual ILogger Logger { get; set; }
 
-        public UnionPayOptions Options { get; protected set; }
+        public virtual IOptionsSnapshot<UnionPayOptions> OptionsSnapshotAccessor { get; set; }
 
         #region UnionPayNotifyClient Constructors
 
         public UnionPayNotifyClient(
             ILogger<UnionPayNotifyClient> logger,
-            IOptions<UnionPayOptions> optionsAccessor)
+            IOptionsSnapshot<UnionPayOptions> optionsAccessor)
         {
             Logger = logger;
-            Options = optionsAccessor.Value;
-
-            if (string.IsNullOrEmpty(Options.MiddleCert))
-            {
-                throw new ArgumentNullException(nameof(Options.MiddleCert));
-            }
-
-            if (string.IsNullOrEmpty(Options.RootCert))
-            {
-                throw new ArgumentNullException(nameof(Options.RootCert));
-            }
+            OptionsSnapshotAccessor = optionsAccessor;
         }
 
         #endregion
@@ -40,6 +30,12 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
 
         public async Task<T> ExecuteAsync<T>(HttpRequest request) where T : UnionPayNotifyResponse
         {
+            return await ExecuteAsync<T>(request, null);
+        }
+
+        public async Task<T> ExecuteAsync<T>(HttpRequest request, string optionsName) where T : UnionPayNotifyResponse
+        {
+            var options = string.IsNullOrEmpty(optionsName) ? OptionsSnapshotAccessor.Value : OptionsSnapshotAccessor.Get(optionsName);
             var parameters = await GetParametersAsync(request);
 
             var query = UnionPayUtility.BuildQuery(parameters);
@@ -47,7 +43,7 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
 
             var parser = new UnionPayDictionaryParser<T>();
             var rsp = parser.Parse(parameters);
-            CheckNotifySign(parameters);
+            CheckNotifySign(parameters, options);
             return rsp;
         }
 
@@ -66,15 +62,15 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
             return parameters;
         }
 
-        private void CheckNotifySign(UnionPayDictionary dic)
+        private void CheckNotifySign(UnionPayDictionary dic, UnionPayOptions options)
         {
             if (dic == null || dic.Count == 0)
             {
                 throw new Exception("sign check fail: sign is Empty!");
             }
 
-            var ifValidateCNName = !Options.TestMode;
-            if (!UnionPaySignature.Validate(dic, Options.RootCertificate.cert, Options.MiddleCertificate.cert, Options.SecureKey, ifValidateCNName))
+            var ifValidateCNName = !options.TestMode;
+            if (!UnionPaySignature.Validate(dic, options.RootCertificate.cert, options.MiddleCertificate.cert, options.SecureKey, ifValidateCNName))
             {
                 throw new Exception("sign check fail: check Sign and Data Fail!");
             }

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

@@ -4,8 +4,6 @@ namespace Essensoft.AspNetCore.Payment.UnionPay
 {
     public class UnionPayOptions
     {
-        public const string DefaultClientName = "Payment.UnionPay.Client";
-
         /// <summary>
         /// 商户代码
         /// </summary>

+ 0 - 14
src/Essensoft.AspNetCore.Payment.UnionPay/UnionPayOptionsAccessor.cs

@@ -1,14 +0,0 @@
-using Microsoft.Extensions.Options;
-
-namespace Essensoft.AspNetCore.Payment.UnionPay
-{
-    public class UnionPayOptionsAccessor : IOptions<UnionPayOptions>
-    {
-        public UnionPayOptionsAccessor(UnionPayOptions options)
-        {
-            Value = options;
-        }
-
-        public UnionPayOptions Value { get; }
-    }
-}

+ 3 - 3
src/Essensoft.AspNetCore.Payment.WeChatPay/Essensoft.AspNetCore.Payment.WeChatPay.csproj

@@ -5,9 +5,9 @@
     <Company>Essensoft</Company>
     <Authors>Roc</Authors>
     <Product>Payment</Product>
-    <Version>1.6.2</Version>
-    <AssemblyVersion>1.6.2.0</AssemblyVersion>
-    <FileVersion>1.6.2.0</FileVersion>
+    <Version>2.0.0</Version>
+    <AssemblyVersion>2.0.0.0</AssemblyVersion>
+    <FileVersion>2.0.0.0</FileVersion>
     <Description>Essensoft.AspNetCore.Payment.WeChatPay</Description>
     <Copyright>© Essensoft 2018</Copyright>
     <PackageProjectUrl>https://github.com/Essensoft/Payment</PackageProjectUrl>

+ 4 - 1
src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayCalcRequest.cs → src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayCallRequest.cs

@@ -2,7 +2,10 @@
 
 namespace Essensoft.AspNetCore.Payment.WeChatPay
 {
-    public interface IWeChatPayCalcRequest
+    /// <summary>
+    /// WeChatPay API调起请求
+    /// </summary>
+    public interface IWeChatPayCallRequest
     {
         /// <summary>
         /// 获取所有的Key-Value形式的文本请求参数字典。其中:

+ 4 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayCertificateRequest.cs

@@ -2,6 +2,10 @@
 
 namespace Essensoft.AspNetCore.Payment.WeChatPay
 {
+    /// <summary>
+    /// WeChatPay API证书请求
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
     public interface IWeChatPayCertificateRequest<T> where T : WeChatPayResponse
     {
         /// <summary>

+ 33 - 6
src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayClient.cs

@@ -2,6 +2,9 @@
 
 namespace Essensoft.AspNetCore.Payment.WeChatPay
 {
+    /// <summary>
+    /// WeChatPay客户端。
+    /// </summary>
     public interface IWeChatPayClient
     {
         /// <summary>
@@ -12,19 +15,43 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
         Task<T> ExecuteAsync<T>(IWeChatPayRequest<T> request) where T : WeChatPayResponse;
 
         /// <summary>
-        /// 执行WeChatPay Certificate API请求。
+        /// 执行WeChatPay API请求。
         /// </summary>
-        /// <param name="request">具体的WeChatPay Certificate API请求</param>
-        /// <param name="certificateName">请求所使用证书的名称</param>
+        /// <param name="request">具体的WeChatPay API请求</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteAsync<T>(IWeChatPayRequest<T> request, string optionsName) where T : WeChatPayResponse;
+
+        /// <summary>
+        /// 执行WeChatPay API证书请求。
+        /// </summary>
+        /// <param name="request">具体的WeChatPay API证书请求</param>
+        /// <param name="certificateName">证书名称</param>
         /// <returns>领域对象</returns>
         Task<T> ExecuteAsync<T>(IWeChatPayCertificateRequest<T> request, string certificateName) where T : WeChatPayResponse;
 
+        /// <summary>
+        /// 执行WeChatPay API证书请求。
+        /// </summary>
+        /// <param name="request">具体的WeChatPay API证书请求</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <param name="certificateName">证书名称</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteAsync<T>(IWeChatPayCertificateRequest<T> request, string optionsName, string certificateName) where T : WeChatPayResponse;
+
+        /// <summary>
+        /// 执行WeChatPay API调起请求。
+        /// </summary>
+        /// <param name="request">具体的WeChatPay API调起请求</param>
+        /// <returns>领域对象</returns>
+        Task<WeChatPayDictionary> ExecuteAsync(IWeChatPayCallRequest request);
 
         /// <summary>
-        /// 执行WeChatPay Calc API请求。
+        /// 执行WeChatPay API调起请求。
         /// </summary>
-        /// <param name="request">具体的WeChatPay Calc API请求</param>
+        /// <param name="request">具体的WeChatPay API调起请求</param>
+        /// <param name="optionsName">配置选项名称</param>
         /// <returns>领域对象</returns>
-        Task<WeChatPayDictionary> ExecuteAsync(IWeChatPayCalcRequest request);
+        Task<WeChatPayDictionary> ExecuteAsync(IWeChatPayCallRequest request, string optionsName);
     }
 }

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

@@ -3,8 +3,26 @@ using Microsoft.AspNetCore.Http;
 
 namespace Essensoft.AspNetCore.Payment.WeChatPay
 {
+    /// <summary>
+    /// WeChatPay通知解析客户端。
+    /// </summary>
     public interface IWeChatPayNotifyClient
     {
+        /// <summary>
+        /// 执行WeChatPay通知请求解析。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">控制器的请求</param>
+        /// <returns>领域对象</returns>
         Task<T> ExecuteAsync<T>(HttpRequest request) where T : WeChatPayNotifyResponse;
+
+        /// <summary>
+        /// 执行WeChatPay通知请求解析。
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">控制器的请求</param>
+        /// <param name="optionsName">配置选项名称</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteAsync<T>(HttpRequest request, string optionsName) where T : WeChatPayNotifyResponse;
     }
 }

+ 4 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayRequest.cs

@@ -2,6 +2,10 @@
 
 namespace Essensoft.AspNetCore.Payment.WeChatPay
 {
+    /// <summary>
+    /// WeChatPay API请求
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
     public interface IWeChatPayRequest<T> where T : WeChatPayResponse
     {
         /// <summary>

+ 1 - 1
src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayAppCallPaymentRequest.cs

@@ -5,7 +5,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay.Request
     /// <summary>
     /// APP支付 调起支付
     /// </summary>
-    public class WeChatPayAppCallPaymentRequest : IWeChatPayCalcRequest
+    public class WeChatPayAppCallPaymentRequest : IWeChatPayCallRequest
     {
         /// <summary>
         /// 应用ID

+ 1 - 1
src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayH5CallPaymentRequest.cs

@@ -5,7 +5,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay.Request
     /// <summary>
     /// 微信内H5调起支付
     /// </summary>
-    public class WeChatPayH5CallPaymentRequest : IWeChatPayCalcRequest
+    public class WeChatPayH5CallPaymentRequest : IWeChatPayCallRequest
     {
         /// <summary>
         /// 公众号ID

+ 1 - 1
src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayLiteAppCallPaymentRequest.cs

@@ -5,7 +5,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay.Request
     /// <summary>
     /// 小程序调起支付
     /// </summary>
-    public class WeChatPayLiteAppCallPaymentRequest : IWeChatPayCalcRequest
+    public class WeChatPayLiteAppCallPaymentRequest : IWeChatPayCallRequest
     {
         /// <summary>
         /// 小程序ID

+ 2 - 31
src/Essensoft.AspNetCore.Payment.WeChatPay/ServiceCollectionExtensions.cs

@@ -1,6 +1,4 @@
 using System;
-using System.Net.Http;
-using System.Security.Cryptography.X509Certificates;
 using Essensoft.AspNetCore.Payment.WeChatPay;
 
 namespace Microsoft.Extensions.DependencyInjection
@@ -17,39 +15,12 @@ namespace Microsoft.Extensions.DependencyInjection
             this IServiceCollection services,
             Action<WeChatPayOptions> setupAction)
         {
-            services.AddSingleton<WeChatPayClient>();
-            services.AddSingleton<WeChatPayNotifyClient>();
-
+            services.AddScoped<IWeChatPayClient, WeChatPayClient>();
+            services.AddScoped<IWeChatPayNotifyClient, WeChatPayNotifyClient>();
             if (setupAction != null)
             {
                 services.Configure(setupAction);
             }
         }
-
-        public static void AddWeChatPayHttpClient(
-            this IServiceCollection services)
-        {
-            services.AddHttpClient(WeChatPayOptions.DefaultClientName);
-        }
-
-        public static void AddWeChatPayCertificateHttpClient(
-            this IServiceCollection services,
-            X509Certificate2 certificate)
-        {
-            services.AddWeChatPayCertificateHttpClient(certificateName: "Default", certificate: certificate);
-        }
-
-        public static void AddWeChatPayCertificateHttpClient(
-            this IServiceCollection services,
-            string certificateName,
-            X509Certificate2 certificate)
-        {
-            services.AddHttpClient(WeChatPayOptions.CertificateClientName + "." + certificateName).ConfigurePrimaryHttpMessageHandler(() =>
-            {
-                var handler = new HttpClientHandler();
-                handler.ClientCertificates.Add(certificate);
-                return handler;
-            });
-        }
     }
 }

+ 57 - 54
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayClient.cs

@@ -35,33 +35,18 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 
         public virtual IHttpClientFactory ClientFactory { get; set; }
 
-        public WeChatPayOptions Options { get; protected set; }
+        public virtual IOptionsSnapshot<WeChatPayOptions> OptionsSnapshotAccessor { get; set; }
 
         #region WeChatPayClient Constructors
 
         public WeChatPayClient(
             ILogger<WeChatPayClient> logger,
             IHttpClientFactory clientFactory,
-            IOptions<WeChatPayOptions> optionsAccessor)
+            IOptionsSnapshot<WeChatPayOptions> optionsAccessor)
         {
             Logger = logger;
             ClientFactory = clientFactory;
-            Options = optionsAccessor.Value;
-
-            if (string.IsNullOrEmpty(Options.AppId))
-            {
-                throw new ArgumentNullException(nameof(Options.AppId));
-            }
-
-            if (string.IsNullOrEmpty(Options.MchId))
-            {
-                throw new ArgumentNullException(nameof(Options.MchId));
-            }
-
-            if (string.IsNullOrEmpty(Options.Key))
-            {
-                throw new ArgumentNullException(nameof(Options.Key));
-            }
+            OptionsSnapshotAccessor = optionsAccessor;
         }
 
         #endregion
@@ -70,19 +55,25 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 
         public async Task<T> ExecuteAsync<T>(IWeChatPayRequest<T> request) where T : WeChatPayResponse
         {
+            return await ExecuteAsync(request, null);
+        }
+
+        public async Task<T> ExecuteAsync<T>(IWeChatPayRequest<T> request, string optionsName) where T : WeChatPayResponse
+        {
+            var options = string.IsNullOrEmpty(optionsName) ? OptionsSnapshotAccessor.Value : OptionsSnapshotAccessor.Get(optionsName);
             // 字典排序
             var sortedTxtParams = new WeChatPayDictionary(request.GetParameters())
             {
-                { mch_id, Options.MchId },
+                { mch_id, options.MchId },
                 { nonce_str, Guid.NewGuid().ToString("N") }
             };
 
             if (string.IsNullOrEmpty(sortedTxtParams.GetValue(appid)))
             {
-                sortedTxtParams.Add(appid, Options.AppId);
+                sortedTxtParams.Add(appid, options.AppId);
             }
 
-            sortedTxtParams.Add(sign, WeChatPaySignature.SignWithKey(sortedTxtParams, Options.Key));
+            sortedTxtParams.Add(sign, WeChatPaySignature.SignWithKey(sortedTxtParams, options.Key));
             var content = WeChatPayUtility.BuildContent(sortedTxtParams);
             Logger?.LogTrace(0, "Request:{content}", content);
 
@@ -93,7 +84,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 
                 var parser = new WeChatPayXmlParser<T>();
                 var rsp = parser.Parse(body);
-                CheckResponseSign(rsp);
+                CheckResponseSign(rsp, options);
                 return rsp;
             }
         }
@@ -102,104 +93,109 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 
         #region IWeChatPayClient Members
 
-        public async Task<T> ExecuteAsync<T>(IWeChatPayCertificateRequest<T> request, string certificateName = "Default") where T : WeChatPayResponse
+        public async Task<T> ExecuteAsync<T>(IWeChatPayCertificateRequest<T> request, string certificateName) where T : WeChatPayResponse
+        {
+            return await ExecuteAsync(request, null, certificateName);
+        }
+
+        public async Task<T> ExecuteAsync<T>(IWeChatPayCertificateRequest<T> request, string optionsName, string certificateName) where T : WeChatPayResponse
         {
             var signType = true; // ture:MD5,false:HMAC-SHA256
             var excludeSignType = true;
-
+            var options = string.IsNullOrEmpty(optionsName) ? OptionsSnapshotAccessor.Value : OptionsSnapshotAccessor.Get(optionsName);
             // 字典排序
             var sortedTxtParams = new WeChatPayDictionary(request.GetParameters());
             if (request is WeChatPayTransfersRequest)
             {
                 if (string.IsNullOrEmpty(sortedTxtParams.GetValue(mch_appid)))
                 {
-                    sortedTxtParams.Add(mch_appid, Options.AppId);
+                    sortedTxtParams.Add(mch_appid, options.AppId);
                 }
 
-                sortedTxtParams.Add(mchid, Options.MchId);
+                sortedTxtParams.Add(mchid, options.MchId);
             }
             else if (request is WeChatPayGetPublicKeyRequest)
             {
-                sortedTxtParams.Add(mch_id, Options.MchId);
+                sortedTxtParams.Add(mch_id, options.MchId);
                 sortedTxtParams.Add(sign_type, "MD5");
                 excludeSignType = false;
             }
             else if (request is WeChatPayPayBankRequest)
             {
-                if (Options.PublicKey == null)
+                if (options.PublicKey == null)
                 {
-                    throw new ArgumentNullException(nameof(Options.RsaPublicKey));
+                    throw new ArgumentNullException(nameof(options.RsaPublicKey));
                 }
 
-                var no = RSA_ECB_OAEPWithSHA1AndMGF1Padding.Encrypt(sortedTxtParams.GetValue(enc_bank_no), Options.PublicKey);
+                var no = RSA_ECB_OAEPWithSHA1AndMGF1Padding.Encrypt(sortedTxtParams.GetValue(enc_bank_no), options.PublicKey);
                 sortedTxtParams.SetValue(enc_bank_no, no);
 
-                var name = RSA_ECB_OAEPWithSHA1AndMGF1Padding.Encrypt(sortedTxtParams.GetValue(enc_true_name), Options.PublicKey);
+                var name = RSA_ECB_OAEPWithSHA1AndMGF1Padding.Encrypt(sortedTxtParams.GetValue(enc_true_name), options.PublicKey);
                 sortedTxtParams.SetValue(enc_true_name, name);
 
-                sortedTxtParams.Add(mch_id, Options.MchId);
+                sortedTxtParams.Add(mch_id, options.MchId);
                 sortedTxtParams.Add(sign_type, "MD5");
             }
             else if (request is WeChatPayQueryBankRequest)
             {
-                sortedTxtParams.Add(mch_id, Options.MchId);
+                sortedTxtParams.Add(mch_id, options.MchId);
                 sortedTxtParams.Add(sign_type, "MD5");
             }
             else if (request is WeChatPayGetTransferInfoRequest)
             {
                 if (string.IsNullOrEmpty(sortedTxtParams.GetValue(appid)))
                 {
-                    sortedTxtParams.Add(appid, Options.AppId);
+                    sortedTxtParams.Add(appid, options.AppId);
                 }
 
-                sortedTxtParams.Add(mch_id, Options.MchId);
+                sortedTxtParams.Add(mch_id, options.MchId);
                 sortedTxtParams.Add(sign_type, "MD5");
             }
             else if (request is WeChatPayDownloadFundFlowRequest)
             {
                 if (string.IsNullOrEmpty(sortedTxtParams.GetValue(appid)))
                 {
-                    sortedTxtParams.Add(appid, Options.AppId);
+                    sortedTxtParams.Add(appid, options.AppId);
                 }
 
-                sortedTxtParams.Add(mch_id, Options.MchId);
+                sortedTxtParams.Add(mch_id, options.MchId);
                 signType = false; // HMAC-SHA256
             }
             else if (request is WeChatPayRefundRequest)
             {
                 if (string.IsNullOrEmpty(sortedTxtParams.GetValue(appid)))
                 {
-                    sortedTxtParams.Add(appid, Options.AppId);
+                    sortedTxtParams.Add(appid, options.AppId);
                 }
 
-                sortedTxtParams.Add(mch_id, Options.MchId);
+                sortedTxtParams.Add(mch_id, options.MchId);
             }
             else if (request is WeChatPaySendRedPackRequest || request is WeChatPaySendGroupRedPackRequest)
             {
                 if (string.IsNullOrEmpty(sortedTxtParams.GetValue(wxappid)))
                 {
-                    sortedTxtParams.Add(wxappid, Options.AppId);
+                    sortedTxtParams.Add(wxappid, options.AppId);
                 }
 
-                sortedTxtParams.Add(mch_id, Options.MchId);
+                sortedTxtParams.Add(mch_id, options.MchId);
             }
             else // 其他接口
             {
                 if (string.IsNullOrEmpty(sortedTxtParams.GetValue(appid)))
                 {
-                    sortedTxtParams.Add(appid, Options.AppId);
+                    sortedTxtParams.Add(appid, options.AppId);
                 }
 
-                sortedTxtParams.Add(mch_id, Options.MchId);
+                sortedTxtParams.Add(mch_id, options.MchId);
             }
 
             sortedTxtParams.Add(nonce_str, Guid.NewGuid().ToString("N"));
-            sortedTxtParams.Add(sign, WeChatPaySignature.SignWithKey(sortedTxtParams, Options.Key, signType, excludeSignType));
+            sortedTxtParams.Add(sign, WeChatPaySignature.SignWithKey(sortedTxtParams, options.Key, signType, excludeSignType));
 
             var content = WeChatPayUtility.BuildContent(sortedTxtParams);
             Logger?.LogTrace(0, "Request:{content}", content);
 
-            using (var client = ClientFactory.CreateClient(WeChatPayOptions.CertificateClientName + "." + certificateName))
+            using (var client = ClientFactory.CreateClient(certificateName))
             {
                 var body = await HttpClientUtility.DoPostAsync(client, request.GetRequestUrl(), content);
 
@@ -207,7 +203,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 
                 var parser = new WeChatPayXmlParser<T>();
                 var rsp = parser.Parse(body);
-                CheckResponseSign(rsp, signType, excludeSignType);
+                CheckResponseSign(rsp, options, signType, excludeSignType);
                 return rsp;
             }
         }
@@ -216,35 +212,42 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 
         #region IWeChatPayClient Members
 
-        public Task<WeChatPayDictionary> ExecuteAsync(IWeChatPayCalcRequest request)
+        public Task<WeChatPayDictionary> ExecuteAsync(IWeChatPayCallRequest request)
         {
+            return ExecuteAsync(request, null);
+        }
+
+        public Task<WeChatPayDictionary> ExecuteAsync(IWeChatPayCallRequest request, string optionsName)
+        {
+            var options = string.IsNullOrEmpty(optionsName) ? OptionsSnapshotAccessor.Value : OptionsSnapshotAccessor.Get(optionsName);
             var sortedTxtParams = new WeChatPayDictionary(request.GetParameters());
+
             if (request is WeChatPayAppCallPaymentRequest)
             {
                 if (string.IsNullOrEmpty(sortedTxtParams.GetValue(appid)))
                 {
-                    sortedTxtParams.Add(appid, Options.AppId);
+                    sortedTxtParams.Add(appid, options.AppId);
                 }
 
                 if (string.IsNullOrEmpty(sortedTxtParams.GetValue(partnerid)))
                 {
-                    sortedTxtParams.Add(partnerid, Options.MchId);
+                    sortedTxtParams.Add(partnerid, options.MchId);
                 }
                 sortedTxtParams.Add(noncestr, Guid.NewGuid().ToString("N"));
                 sortedTxtParams.Add(timestamp, WeChatPayUtility.GetTimeStamp());
-                sortedTxtParams.Add(sign, WeChatPaySignature.SignWithKey(sortedTxtParams, Options.Key));
+                sortedTxtParams.Add(sign, WeChatPaySignature.SignWithKey(sortedTxtParams, options.Key));
             }
             else if (request is WeChatPayLiteAppCallPaymentRequest || request is WeChatPayH5CallPaymentRequest)
             {
                 if (string.IsNullOrEmpty(sortedTxtParams.GetValue(appId)))
                 {
-                    sortedTxtParams.Add(appId, Options.AppId);
+                    sortedTxtParams.Add(appId, options.AppId);
                 }
 
                 sortedTxtParams.Add(timeStamp, WeChatPayUtility.GetTimeStamp());
                 sortedTxtParams.Add(nonceStr, Guid.NewGuid().ToString("N"));
                 sortedTxtParams.Add(signType, "MD5");
-                sortedTxtParams.Add(paySign, WeChatPaySignature.SignWithKey(sortedTxtParams, Options.Key));
+                sortedTxtParams.Add(paySign, WeChatPaySignature.SignWithKey(sortedTxtParams, options.Key));
             }
             return Task.FromResult(sortedTxtParams);
         }
@@ -253,7 +256,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 
         #region Common Method
 
-        private void CheckResponseSign(WeChatPayResponse response, bool useMD5 = true, bool excludeSignType = true)
+        private void CheckResponseSign(WeChatPayResponse response, WeChatPayOptions options, bool useMD5 = true, bool excludeSignType = true)
         {
             if (string.IsNullOrEmpty(response.Body))
             {
@@ -264,7 +267,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
             {
                 if (response.Parameters["return_code"] == "SUCCESS" && !string.IsNullOrEmpty(sign))
                 {
-                    var cal_sign = WeChatPaySignature.SignWithKey(response.Parameters, Options.Key, useMD5, excludeSignType);
+                    var cal_sign = WeChatPaySignature.SignWithKey(response.Parameters, options.Key, useMD5, excludeSignType);
                     if (cal_sign != sign)
                     {
                         throw new Exception("sign check fail: check Sign and Data Fail!");

+ 13 - 12
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayNotifyClient.cs

@@ -16,21 +16,16 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
     {
         public virtual ILogger Logger { get; set; }
 
-        public WeChatPayOptions Options { get; protected set; }
+        public virtual IOptionsSnapshot<WeChatPayOptions> OptionsSnapshotAccessor { get; set; }
 
         #region WeChatPayNotifyClient Constructors
 
         public WeChatPayNotifyClient(
             ILogger<WeChatPayNotifyClient> logger,
-            IOptions<WeChatPayOptions> optionsAccessor)
+            IOptionsSnapshot<WeChatPayOptions> optionsAccessor)
         {
             Logger = logger;
-            Options = optionsAccessor.Value;
-
-            if (string.IsNullOrEmpty(Options.Key))
-            {
-                throw new ArgumentNullException(nameof(Options.Key));
-            }
+            OptionsSnapshotAccessor = optionsAccessor;
         }
 
         #endregion
@@ -39,6 +34,12 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 
         public async Task<T> ExecuteAsync<T>(HttpRequest request) where T : WeChatPayNotifyResponse
         {
+            return await ExecuteAsync<T>(request, null);
+        }
+
+        public async Task<T> ExecuteAsync<T>(HttpRequest request, string optionsName) where T : WeChatPayNotifyResponse
+        {
+            var options = string.IsNullOrEmpty(optionsName) ? OptionsSnapshotAccessor.Value : OptionsSnapshotAccessor.Get(optionsName);
             var body = await new StreamReader(request.Body, Encoding.UTF8).ReadToEndAsync();
             Logger?.LogTrace(0, "Request:{body}", body);
 
@@ -46,14 +47,14 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
             var rsp = parser.Parse(body);
             if (rsp is WeChatPayRefundNotifyResponse)
             {
-                var key = MD5.Compute(Options.Key).ToLower();
+                var key = MD5.Compute(options.Key).ToLower();
                 var data = AES.Decrypt((rsp as WeChatPayRefundNotifyResponse).ReqInfo, key, AESCipherMode.ECB, AESPaddingMode.PKCS7);
                 Logger?.LogTrace(1, "Decrypt Content:{data}", data); // AES-256-ECB
                 rsp = parser.Parse(body, data);
             }
             else
             {
-                CheckNotifySign(rsp);
+                CheckNotifySign(rsp, options);
             }
             return rsp;
         }
@@ -62,7 +63,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 
         #region Common Method
 
-        private void CheckNotifySign(WeChatPayNotifyResponse response)
+        private void CheckNotifySign(WeChatPayNotifyResponse response, WeChatPayOptions options)
         {
             if (response?.Parameters?.Count == 0)
             {
@@ -74,7 +75,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
                 throw new Exception("sign check fail: sign is Empty!");
             }
 
-            var cal_sign = WeChatPaySignature.SignWithKey(response.Parameters, Options.Key);
+            var cal_sign = WeChatPaySignature.SignWithKey(response.Parameters, options.Key);
             if (cal_sign != sign)
             {
                 throw new Exception("sign check fail: check Sign and Data Fail!");

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

@@ -5,9 +5,6 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 {
     public class WeChatPayOptions
     {
-        public const string DefaultClientName = "Payment.WechatPay.Client";
-        public const string CertificateClientName = "Payment.WechatPay.CertificateClient";
-
         /// <summary>
         /// 应用账号(公众账号ID/小程序ID/企业号CorpId)
         /// </summary>

+ 0 - 14
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayOptionsAccessor.cs

@@ -1,14 +0,0 @@
-using Microsoft.Extensions.Options;
-
-namespace Essensoft.AspNetCore.Payment.WeChatPay
-{
-    public class WeChatPayOptionsAccessor : IOptions<WeChatPayOptions>
-    {
-        public WeChatPayOptionsAccessor(WeChatPayOptions options)
-        {
-            Value = options;
-        }
-
-        public WeChatPayOptions Value { get; }
-    }
-}