Просмотр исходного кода

Merge pull request #74 from essensoft/dev-wechatpay-apiv3

实现 微信支付V3-基础支付-普通支付(直连模式)
Roc 5 лет назад
Родитель
Сommit
0bc4f6411d
99 измененных файлов с 3952 добавлено и 101 удалено
  1. 40 0
      samples/WebApplicationSample/Controllers/NotifyController.cs
  2. 2 1
      samples/WebApplicationSample/Controllers/WeChatPayController.cs
  3. 316 0
      samples/WebApplicationSample/Controllers/WeChatPayV3Controller.cs
  4. 105 0
      samples/WebApplicationSample/Models/WeChatPayV3ViewModel.cs
  5. 3 0
      samples/WebApplicationSample/Views/Shared/_Layout.cshtml
  6. 49 0
      samples/WebApplicationSample/Views/WeChatPayV3/AppPay.cshtml
  7. 24 0
      samples/WebApplicationSample/Views/WeChatPayV3/GetCertificates.cshtml
  8. 45 0
      samples/WebApplicationSample/Views/WeChatPayV3/H5Pay.cshtml
  9. 70 0
      samples/WebApplicationSample/Views/WeChatPayV3/Index.cshtml
  10. 33 0
      samples/WebApplicationSample/Views/WeChatPayV3/OutTradeNoClose.cshtml
  11. 53 0
      samples/WebApplicationSample/Views/WeChatPayV3/PubPay.cshtml
  12. 52 0
      samples/WebApplicationSample/Views/WeChatPayV3/QrCodePay.cshtml
  13. 33 0
      samples/WebApplicationSample/Views/WeChatPayV3/QueryByOutTradeNo.cshtml
  14. 33 0
      samples/WebApplicationSample/Views/WeChatPayV3/QueryByTransactionId.cshtml
  15. 5 1
      samples/WebApplicationSample/appsettings.Development.json
  16. 5 1
      samples/WebApplicationSample/appsettings.json
  17. 44 0
      src/Essensoft.AspNetCore.Payment.Security/AEAD_AES_256_GCM.cs
  18. 26 0
      src/Essensoft.AspNetCore.Payment.Security/SHA256WithRSA.cs
  19. 26 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/Amount.cs
  20. 34 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/Certificate.cs
  21. 1 1
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/CouponInfo.cs
  22. 1 1
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/CouponRefundInfo.cs
  23. 37 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/Detail.cs
  24. 34 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/EncryptCertificate.cs
  25. 16 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/ErrorDetail.cs
  26. 50 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/GoodsDetail.cs
  27. 1 1
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/HBInfo.cs
  28. 50 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/NotifyCiphertext.cs
  29. 18 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/Payer.cs
  30. 100 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/PromotionDetail.cs
  31. 50 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/PromotionGoodsDetail.cs
  32. 42 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/QueryAmount.cs
  33. 45 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/Receiver.cs
  34. 1 1
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/RefundInfo.cs
  35. 42 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/Resource.cs
  36. 33 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/SceneInfo.cs
  37. 42 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/StoreInfo.cs
  38. 99 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/WeChatPayTransactionsAppModel.cs
  39. 99 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/WeChatPayTransactionsH5Model.cs
  40. 102 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/WeChatPayTransactionsJsApiModel.cs
  41. 99 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/WeChatPayTransactionsNativeModel.cs
  42. 20 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/WeChatPayTransactionsOutTradeNoCloseModel.cs
  43. 1 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Essensoft.AspNetCore.Payment.WeChatPay.csproj
  44. 30 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayV3Client.cs
  45. 10 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayV3GetRequest.cs
  46. 21 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayV3NotifyClient.cs
  47. 21 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayV3PostRequest.cs
  48. 22 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayV3SdkRequest.cs
  49. 73 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Notify/WeChatPayProfitSharingNotify.cs
  50. 140 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Notify/WeChatPayTransactionsNotify.cs
  51. 13 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/IWeChatPayNotifyParser.cs
  52. 7 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/IWeChatPayObjectJsonParser.cs
  53. 0 9
      src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/IWeChatPayParser.cs
  54. 7 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/IWeChatPayResponseParser.cs
  55. 13 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/IWeChatPayV3NotifyJsonParser.cs
  56. 7 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/IWeChatPayV3ResponseJsonParser.cs
  57. 6 2
      src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/WeChatPayNotifyXmlParser.cs
  58. 32 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/WeChatPayObjectJsonParser.cs
  59. 42 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/WeChatPayResponseXmlParser.cs
  60. 60 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/WeChatPayV3NotifyJsonParser.cs
  61. 34 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/WeChatPayV3ResponseJsonParser.cs
  62. 16 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayCertificatesRequest.cs
  63. 29 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayTransactionsAppRequest.cs
  64. 29 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayTransactionsH5Request.cs
  65. 31 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayTransactionsIdRequest.cs
  66. 29 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayTransactionsJsApiRequest.cs
  67. 29 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayTransactionsNativeRequest.cs
  68. 37 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayTransactionsOutTradeNoCloseRequest.cs
  69. 32 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayTransactionsOutTradeNoRequest.cs
  70. 67 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayV3AppSdkRequest.cs
  71. 56 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayV3JsApiSdkRequest.cs
  72. 12 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Response/WeChatPayCertificatesResponse.cs
  73. 20 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Response/WeChatPayTransactionsAppResponse.cs
  74. 20 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Response/WeChatPayTransactionsH5Response.cs
  75. 136 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Response/WeChatPayTransactionsIdResponse.cs
  76. 20 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Response/WeChatPayTransactionsJsApiResponse.cs
  77. 21 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Response/WeChatPayTransactionsNativeResponse.cs
  78. 11 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Response/WeChatPayTransactionsOutTradeNoCloseResponse.cs
  79. 136 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/Response/WeChatPayTransactionsOutTradeNoResponse.cs
  80. 4 1
      src/Essensoft.AspNetCore.Payment.WeChatPay/ServiceCollectionExtensions.cs
  81. 101 10
      src/Essensoft.AspNetCore.Payment.WeChatPay/Utility/HttpClientExtensions.cs
  82. 1 1
      src/Essensoft.AspNetCore.Payment.WeChatPay/Utility/WeChatPaySignature.cs
  83. 0 16
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayCertificateManager.cs
  84. 17 18
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayClient.cs
  85. 16 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayClientCertificateManager.cs
  86. 8 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayConsts.cs
  87. 5 5
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayHandlerBuilderFilter.cs
  88. 18 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayNotify.cs
  89. 1 1
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayNotifyClient.cs
  90. 1 19
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayObject.cs
  91. 28 11
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayOptions.cs
  92. 16 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayPlatformCertificateManager.cs
  93. 19 1
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayResponse.cs
  94. 43 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayTradeState.cs
  95. 204 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayV3Client.cs
  96. 30 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayV3Notify.cs
  97. 125 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayV3NotifyClient.cs
  98. 27 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayV3NotifyResult.cs
  99. 38 0
      src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayV3Response.cs

+ 40 - 0
samples/WebApplicationSample/Controllers/NotifyController.cs

@@ -333,4 +333,44 @@ namespace WebApplicationSample.Controllers
     }
 
     #endregion
+
+    #region 微信支付V3异步通知
+
+    [Route("notify/wechatpay/v3")]
+    public class WeChatPayV3NotifyController : Controller
+    {
+        private readonly IWeChatPayV3NotifyClient _client;
+        private readonly IOptions<WeChatPayOptions> _optionsAccessor;
+
+        public WeChatPayV3NotifyController(IWeChatPayV3NotifyClient client, IOptions<WeChatPayOptions> optionsAccessor)
+        {
+            _client = client;
+            _optionsAccessor = optionsAccessor;
+        }
+
+        /// <summary>
+        /// 支付结果通知
+        /// </summary>
+        [Route("transactions")]
+        [HttpPost]
+        public async Task<IActionResult> Transactions()
+        {
+            try
+            {
+                var notify = await _client.ExecuteAsync<WeChatPayTransactionsNotify>(Request, _optionsAccessor.Value);
+                if (notify.TradeState == WeChatPayTradeState.Success)
+                {
+                    Console.WriteLine("OutTradeNo: " + notify.OutTradeNo);
+                    return WeChatPayV3NotifyResult.Success;
+                }
+                return NoContent();
+            }
+            catch
+            {
+                return NoContent();
+            }
+        }
+    }
+
+    #endregion
 }

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

@@ -212,7 +212,8 @@ namespace WebApplicationSample.Controllers
             var response = await _client.ExecuteAsync(request, _optionsAccessor.Value);
 
             // mweb_url为拉起微信支付收银台的中间页面,可通过访问该url来拉起微信客户端,完成支付,mweb_url的有效期为5分钟。
-            return Redirect(response.MwebUrl);
+            ViewData["response"] = response.Body;
+            return View();
         }
 
         /// <summary>

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

@@ -0,0 +1,316 @@
+using System.Text.Json;
+using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.WeChatPay;
+using Essensoft.AspNetCore.Payment.WeChatPay.Domain;
+using Essensoft.AspNetCore.Payment.WeChatPay.Request;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Options;
+using WebApplicationSample.Models;
+
+namespace WebApplicationSample.Controllers
+{
+    public class WeChatPayV3Controller : Controller
+    {
+        private readonly IWeChatPayV3Client _client;
+        private readonly IOptions<WeChatPayOptions> _optionsAccessor;
+
+        public WeChatPayV3Controller(IWeChatPayV3Client client, IOptions<WeChatPayOptions> optionsAccessor)
+        {
+            _client = client;
+            _optionsAccessor = optionsAccessor;
+        }
+
+        /// <summary>
+        /// 微信支付指引页
+        /// </summary>
+        public IActionResult Index()
+        {
+            return View();
+        }
+
+        /// <summary>
+        /// 获取平台证书列表
+        /// </summary>
+        [HttpGet]
+        [HttpPost]
+        public async Task<IActionResult> GetCertificates()
+        {
+            if (Request.Method == "POST")
+            {
+                var request = new WeChatPayCertificatesRequest();
+                var response = await _client.ExecuteAsync(request, _optionsAccessor.Value);
+                ViewData["response"] = response.Body;
+                return View();
+            }
+
+            return View();
+        }
+
+        /// <summary>
+        /// APP支付-App下单API
+        /// </summary>
+        [HttpGet]
+        public IActionResult AppPay()
+        {
+            return View();
+        }
+
+        /// <summary>
+        /// APP支付-App下单API
+        /// </summary>
+        /// <param name="viewModel"></param>
+        [HttpPost]
+        public async Task<IActionResult> AppPay(WeChatPayAppPayV3ViewModel viewModel)
+        {
+            var model = new WeChatPayTransactionsAppModel
+            {
+                AppId = _optionsAccessor.Value.AppId,
+                MchId = _optionsAccessor.Value.MchId,
+                Amount = new Amount { Total = viewModel.Total, Currency = "CNY" },
+                Description = viewModel.Description,
+                NotifyUrl = viewModel.NotifyUrl,
+                OutTradeNo = viewModel.OutTradeNo,
+            };
+
+            var request = new WeChatPayTransactionsAppRequest();
+            request.SetQueryModel(model);
+
+            var response = await _client.ExecuteAsync(request, _optionsAccessor.Value);
+
+            if (response.StatusCode == 200)
+            {
+                var req = new WeChatPayV3AppSdkRequest
+                {
+                    PrepayId = response.PrepayId
+                };
+
+                var parameter = await _client.ExecuteAsync(req, _optionsAccessor.Value);
+
+                // 将参数(parameter)给 ios/android端 
+                // https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_7.shtml
+                ViewData["parameter"] = JsonSerializer.Serialize(parameter);
+                ViewData["response"] = response.Body;
+                return View();
+            }
+
+            ViewData["response"] = response.Body;
+            return View();
+        }
+
+        /// <summary>
+        /// 公众号支付-JSAPI下单
+        /// </summary>
+        [HttpGet]
+        public IActionResult PubPay()
+        {
+            return View();
+        }
+
+        /// <summary>
+        /// 公众号支付-JSAPI下单
+        /// </summary>
+        /// <param name="viewModel"></param>
+        [HttpPost]
+        public async Task<IActionResult> PubPay(WeChatPayPubPayV3ViewModel viewModel)
+        {
+            var model = new WeChatPayTransactionsJsApiModel
+            {
+                AppId = _optionsAccessor.Value.AppId,
+                MchId = _optionsAccessor.Value.MchId,
+                Amount = new Amount { Total = viewModel.Total, Currency = "CNY" },
+                Description = viewModel.Description,
+                NotifyUrl = viewModel.NotifyUrl,
+                OutTradeNo = viewModel.OutTradeNo,
+                Payer = new Payer { OpenId = viewModel.OpenId }
+            };
+
+            var request = new WeChatPayTransactionsJsApiRequest();
+            request.SetQueryModel(model);
+
+            var response = await _client.ExecuteAsync(request, _optionsAccessor.Value);
+
+            if (response.StatusCode == 200)
+            {
+                var req = new WeChatPayV3JsApiSdkRequest
+                {
+                    Package = "prepay_id=" + response.PrepayId
+                };
+
+                var parameter = await _client.ExecuteAsync(req, _optionsAccessor.Value);
+
+                // 将参数(parameter)给 公众号前端
+                // https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_8.shtml
+                ViewData["parameter"] = JsonSerializer.Serialize(parameter);
+                ViewData["response"] = response.Body;
+                return View();
+            }
+
+            ViewData["response"] = response.Body;
+            return View();
+        }
+
+        /// <summary>
+        /// 扫码支付-Native下单API
+        /// </summary>
+        [HttpGet]
+        public IActionResult QrCodePay()
+        {
+            return View();
+        }
+
+        /// <summary>
+        /// 扫码支付-Native下单API
+        /// </summary>
+        /// <param name="viewModel"></param>
+        [HttpPost]
+        public async Task<IActionResult> QrCodePay(WeChatPayQrCodePayV3ViewModel viewModel)
+        {
+            var model = new WeChatPayTransactionsNativeModel
+            {
+                AppId = _optionsAccessor.Value.AppId,
+                MchId = _optionsAccessor.Value.MchId,
+                Amount = new Amount { Total = viewModel.Total, Currency = "CNY" },
+                Description = viewModel.Description,
+                NotifyUrl = viewModel.NotifyUrl,
+                OutTradeNo = viewModel.OutTradeNo,
+            };
+
+            var request = new WeChatPayTransactionsNativeRequest();
+            request.SetQueryModel(model);
+
+            var response = await _client.ExecuteAsync(request, _optionsAccessor.Value);
+
+            // response.CodeUrl 给前端生成二维码
+            ViewData["qrcode"] = response.CodeUrl;
+            ViewData["response"] = response.Body;
+            return View();
+        }
+
+        /// <summary>
+        /// H5支付-H5下单API
+        /// </summary>
+        [HttpGet]
+        public IActionResult H5Pay()
+        {
+            return View();
+        }
+
+        /// <summary>
+        /// H5支付-H5下单API
+        /// </summary>
+        /// <param name="viewModel"></param>
+        [HttpPost]
+        public async Task<IActionResult> H5Pay(WeChatPayH5PayV3ViewModel viewModel)
+        {
+            var model = new WeChatPayTransactionsH5Model
+            {
+                AppId = _optionsAccessor.Value.AppId,
+                MchId = _optionsAccessor.Value.MchId,
+                Amount = new Amount { Total = viewModel.Total, Currency = "CNY" },
+                Description = viewModel.Description,
+                NotifyUrl = viewModel.NotifyUrl,
+                OutTradeNo = viewModel.OutTradeNo,
+                SceneInfo = new SceneInfo { PayerClientIp = "127.0.0.1" }
+            };
+
+            var request = new WeChatPayTransactionsH5Request();
+            request.SetQueryModel(model);
+
+            var response = await _client.ExecuteAsync(request, _optionsAccessor.Value);
+
+            // h5_url为拉起微信支付收银台的中间页面,可通过访问该url来拉起微信客户端,完成支付,h5_url的有效期为5分钟。
+            ViewData["response"] = response.Body;
+            return View();
+        }
+
+        /// <summary>
+        /// 微信支付订单号查询
+        /// </summary>
+        [HttpGet]
+        public IActionResult QueryByTransactionId()
+        {
+            return View();
+        }
+
+        /// <summary>
+        /// 微信支付订单号查询
+        /// </summary>
+        /// <param name="viewModel"></param>
+        [HttpPost]
+        public async Task<IActionResult> QueryByTransactionId(WeChatPayQueryByTransactionIdViewModel viewModel)
+        {
+            var request = new WeChatPayTransactionsIdRequest
+            { 
+                TransactionId = viewModel.TransactionId,
+                MchId = _optionsAccessor.Value.MchId,
+            };
+
+            var response = await _client.ExecuteAsync(request, _optionsAccessor.Value);
+
+            ViewData["response"] = response.Body;
+            return View();
+        }
+
+        /// <summary>
+        /// 商户订单号查询
+        /// </summary>
+        [HttpGet]
+        public IActionResult QueryByOutTradeNo()
+        {
+            return View();
+        }
+
+        /// <summary>
+        /// 商户订单号查询
+        /// </summary>
+        /// <param name="viewModel"></param>
+        [HttpPost]
+        public async Task<IActionResult> QueryByOutTradeNo(WeChatPayQueryByOutTradeNoViewModel viewModel)
+        {
+            var request = new WeChatPayTransactionsOutTradeNoRequest
+            {
+                OutTradeNo = viewModel.OutTradeNo,
+                MchId = _optionsAccessor.Value.MchId,
+            };
+
+            var response = await _client.ExecuteAsync(request, _optionsAccessor.Value);
+
+            ViewData["response"] = response.Body;
+            return View();
+        }
+
+        /// <summary>
+        /// 关闭订单
+        /// </summary>
+        [HttpGet]
+        public IActionResult OutTradeNoClose()
+        {
+            return View();
+        }
+
+        /// <summary>
+        /// 关闭订单
+        /// </summary>
+        /// <param name="viewModel"></param>
+        [HttpPost]
+        public async Task<IActionResult> OutTradeNoClose(WeChatPayOutTradeNoCloseViewModel viewModel)
+        {
+            var model = new WeChatPayTransactionsOutTradeNoCloseModel
+            {
+                MchId = _optionsAccessor.Value.MchId,
+            };
+
+            var request = new WeChatPayTransactionsOutTradeNoCloseRequest
+            {
+                OutTradeNo = viewModel.OutTradeNo,
+            };
+
+            request.SetQueryModel(model);
+
+            var response = await _client.ExecuteAsync(request, _optionsAccessor.Value);
+
+            ViewData["response"] = response.Body;
+            return View();
+        }
+    }
+}

+ 105 - 0
samples/WebApplicationSample/Models/WeChatPayV3ViewModel.cs

@@ -0,0 +1,105 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace WebApplicationSample.Models
+{
+    public class WeChatPayAppPayV3ViewModel
+    {
+        [Required]
+        [Display(Name = "out_trade_no")]
+        public string OutTradeNo { get; set; }
+
+        [Required]
+        [Display(Name = "description")]
+        public string Description { get; set; }
+
+        [Required]
+        [Display(Name = "total")]
+        public int Total { get; set; }
+
+        [Required]
+        [Display(Name = "notify_url")]
+        public string NotifyUrl { get; set; }
+    }
+
+    public class WeChatPayPubPayV3ViewModel
+    {
+        [Required]
+        [Display(Name = "out_trade_no")]
+        public string OutTradeNo { get; set; }
+
+        [Required]
+        [Display(Name = "description")]
+        public string Description { get; set; }
+
+        [Required]
+        [Display(Name = "total")]
+        public int Total { get; set; }
+
+        [Required]
+        [Display(Name = "notify_url")]
+        public string NotifyUrl { get; set; }
+
+        [Required]
+        [Display(Name = "openid")]
+        public string OpenId { get; set; }
+    }
+
+    public class WeChatPayQrCodePayV3ViewModel
+    {
+        [Required]
+        [Display(Name = "out_trade_no")]
+        public string OutTradeNo { get; set; }
+
+        [Required]
+        [Display(Name = "description")]
+        public string Description { get; set; }
+
+        [Required]
+        [Display(Name = "total")]
+        public int Total { get; set; }
+
+        [Required]
+        [Display(Name = "notify_url")]
+        public string NotifyUrl { get; set; }
+    }
+
+    public class WeChatPayH5PayV3ViewModel
+    {
+        [Required]
+        [Display(Name = "out_trade_no")]
+        public string OutTradeNo { get; set; }
+
+        [Required]
+        [Display(Name = "description")]
+        public string Description { get; set; }
+
+        [Required]
+        [Display(Name = "total")]
+        public int Total { get; set; }
+
+        [Required]
+        [Display(Name = "notify_url")]
+        public string NotifyUrl { get; set; }
+    }
+
+    public class WeChatPayQueryByTransactionIdViewModel
+    {
+        [Required]
+        [Display(Name = "transaction_id")]
+        public string TransactionId { get; set; }
+    }
+
+    public class WeChatPayQueryByOutTradeNoViewModel
+    {
+        [Required]
+        [Display(Name = "out_trade_no")]
+        public string OutTradeNo { get; set; }
+    }
+
+    public class WeChatPayOutTradeNoCloseViewModel
+    {
+        [Required]
+        [Display(Name = "out_trade_no")]
+        public string OutTradeNo { get; set; }
+    }
+}

+ 3 - 0
samples/WebApplicationSample/Views/Shared/_Layout.cshtml

@@ -34,6 +34,9 @@
                     <li class="nav-item @Html.IsActive("WeChatPay")">
                         <a class="nav-link" asp-controller="WeChatPay" asp-action="Index">微信支付</a>
                     </li>
+                    <li class="nav-item @Html.IsActive("WeChatPayV3")">
+                        <a class="nav-link" asp-controller="WeChatPayV3" asp-action="Index">微信支付V3</a>
+                    </li>
                 </ul>
             </div>
         </div>

+ 49 - 0
samples/WebApplicationSample/Views/WeChatPayV3/AppPay.cshtml

@@ -0,0 +1,49 @@
+@model WeChatPayAppPayV3ViewModel
+@{
+    ViewData["Title"] = "APP支付-APP下单API";
+}
+<nav aria-label="breadcrumb">
+    <ol class="breadcrumb">
+        <li class="breadcrumb-item"><a asp-controller="WeChatPayV3" asp-action="Index">微信支付V3</a></li>
+        <li class="breadcrumb-item active" aria-current="page">@ViewData["Title"]</li>
+    </ol>
+</nav>
+<br />
+<div class="card">
+    <div class="card-body">
+        <form asp-controller="WeChatPayV3" asp-action="AppPay">
+            <div asp-validation-summary="All" class="text-danger"></div>
+            <div class="form-group">
+                <label asp-for="OutTradeNo"></label>
+                <input type="text" class="form-control" asp-for="OutTradeNo" value="@DateTime.Now.ToString("yyyyMMddHHmmssfff")" />
+            </div>
+            <div class="form-group">
+                <label asp-for="Description"></label>
+                <input type="text" class="form-control" asp-for="Description" value="微信APP支付测试" />
+            </div>
+            <div class="form-group">
+                <label asp-for="Total"></label>
+                <input type="text" class="form-control" asp-for="Total" value="1" />
+            </div>
+            <div class="form-group">
+                <label asp-for="NotifyUrl"></label>
+                <input type="text" class="form-control" asp-for="NotifyUrl" value="http://*/notify/wechatpay/v3/transactions" />
+            </div>
+            <button type="submit" class="btn btn-primary">提交请求</button>
+        </form>
+        <hr />
+        <form class="form-horizontal">
+            <div class="form-group">
+                <label>Response:</label>
+                <textarea class="form-control" rows="10">@ViewData["response"]</textarea>
+            </div>
+            <div class="form-group">
+                <label>Parameter:</label>
+                <textarea class="form-control" rows="3">@ViewData["parameter"]</textarea>
+            </div>
+        </form>
+    </div>
+</div>
+@section Scripts {
+    @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
+}

+ 24 - 0
samples/WebApplicationSample/Views/WeChatPayV3/GetCertificates.cshtml

@@ -0,0 +1,24 @@
+@{
+    ViewData["Title"] = "获取平台证书列表";
+}
+<nav aria-label="breadcrumb">
+    <ol class="breadcrumb">
+        <li class="breadcrumb-item"><a asp-controller="WeChatPayV3" asp-action="Index">微信支付V3</a></li>
+        <li class="breadcrumb-item active" aria-current="page">@ViewData["Title"]</li>
+    </ol>
+</nav>
+<br />
+<div class="card">
+    <div class="card-body">
+        <form asp-controller="WeChatPayV3" asp-action="GetCertificates">
+            <button type="submit" class="btn btn-primary">提交请求</button>
+        </form>
+        <hr />
+        <form class="form-horizontal">
+            <div class="form-group">
+                <label>Response:</label>
+                <textarea class="form-control" rows="10">@ViewData["response"]</textarea>
+            </div>
+        </form>
+    </div>
+</div>

+ 45 - 0
samples/WebApplicationSample/Views/WeChatPayV3/H5Pay.cshtml

@@ -0,0 +1,45 @@
+@model WeChatPayH5PayV3ViewModel
+@{
+    ViewData["Title"] = "H5支付-H5下单API";
+}
+<nav aria-label="breadcrumb">
+    <ol class="breadcrumb">
+        <li class="breadcrumb-item"><a asp-controller="WeChatPayV3" asp-action="Index">微信支付V3</a></li>
+        <li class="breadcrumb-item active" aria-current="page">@ViewData["Title"]</li>
+    </ol>
+</nav>
+<br />
+<div class="card">
+    <div class="card-body">
+        <form asp-controller="WeChatPayV3" asp-action="H5Pay">
+            <div asp-validation-summary="All" class="text-danger"></div>
+            <div class="form-group">
+                <label asp-for="OutTradeNo"></label>
+                <input type="text" class="form-control" asp-for="OutTradeNo" value="@DateTime.Now.ToString("yyyyMMddHHmmssfff")" />
+            </div>
+            <div class="form-group">
+                <label asp-for="Description"></label>
+                <input type="text" class="form-control" asp-for="Description" value="微信H5支付测试" />
+            </div>
+            <div class="form-group">
+                <label asp-for="Total"></label>
+                <input type="text" class="form-control" asp-for="Total" value="1">
+            </div>
+            <div class="form-group">
+                <label asp-for="NotifyUrl"></label>
+                <input type="text" class="form-control" asp-for="NotifyUrl" value="http://*/notify/wechatpay/v3/transactions">
+            </div>
+            <button type="submit" class="btn btn-primary">提交请求</button>
+        </form>
+        <hr />
+        <form class="form-horizontal">
+            <div class="form-group">
+                <label>Response:</label>
+                <textarea class="form-control" rows="10">@ViewData["response"]</textarea>
+            </div>
+        </form>
+    </div>
+</div>
+@section Scripts {
+    @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
+}

+ 70 - 0
samples/WebApplicationSample/Views/WeChatPayV3/Index.cshtml

@@ -0,0 +1,70 @@
+@{
+    ViewData["Title"] = "微信支付V3";
+}
+<nav aria-label="breadcrumb">
+    <ol class="breadcrumb">
+        <li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index">首页</a></li>
+        <li class="breadcrumb-item active" aria-current="page">@ViewData["Title"]</li>
+    </ol>
+</nav>
+<br />
+<table class="table table-bordered table-hover">
+    <thead>
+        <tr>
+            <th scope="col">#</th>
+            <th scope="col">产品</th>
+            <th scope="col">接口</th>
+            <th scope="col">#</th>
+        </tr>
+    </thead>
+    <tbody>
+        <tr>
+            <th scope="row">1</th>
+            <td>获取平台证书列表</td>
+            <td><a href="https://wechatpay-api.gitbook.io/wechatpay-api-v3/jie-kou-wen-dang/ping-tai-zheng-shu" target="_blank">https://api.mch.weixin.qq.com/v3/certificates</a></td>
+            <td><a asp-controller="WeChatPayV3" asp-action="GetCertificates">立即测试</a></td>
+        </tr>
+        <tr>
+            <th scope="row">2</th>
+            <td>APP支付-App下单</td>
+            <td><a href="https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_1.shtml" target="_blank">https://api.mch.weixin.qq.com/v3/pay/transactions/app</a></td>
+            <td><a asp-controller="WeChatPayV3" asp-action="AppPay">立即测试</a></td>
+        </tr>
+        <tr>
+            <th scope="row">3</th>
+            <td>公众号支付-JSAPI下单</td>
+            <td><a href="https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_2.shtml" target="_blank">https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi</a></td>
+            <td><a asp-controller="WeChatPayV3" asp-action="PubPay">立即测试</a></td>
+        </tr>
+        <tr>
+            <th scope="row">4</th>
+            <td>扫码支付-Native下单</td>
+            <td><a href="https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_3.shtml" target="_blank">https://api.mch.weixin.qq.com/v3/pay/transactions/native</a></td>
+            <td><a asp-controller="WeChatPayV3" asp-action="QrCodePay">立即测试</a></td>
+        </tr>
+        <tr>
+            <th scope="row">5</th>
+            <td>H5支付-H5下单</td>
+            <td><a href="https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_4.shtml" target="_blank">https://api.mch.weixin.qq.com/v3/pay/transactions/h5</a></td>
+            <td><a asp-controller="WeChatPayV3" asp-action="H5Pay">立即测试</a></td>
+        </tr>
+        <tr>
+            <th scope="row">6</th>
+            <td>查询订单-微信支付订单号查询</td>
+            <td><a href="https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_5.shtml" target="_blank">https://api.mch.weixin.qq.com/v3/pay/transactions/id/{transaction_id}</a></td>
+            <td><a asp-controller="WeChatPayV3" asp-action="QueryByTransactionId">立即测试</a></td>
+        </tr>
+        <tr>
+            <th scope="row">7</th>
+            <td>查询订单-商户订单号查询</td>
+            <td><a href="https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_5.shtml" target="_blank">https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}</a></td>
+            <td><a asp-controller="WeChatPayV3" asp-action="QueryByOutTradeNo">立即测试</a></td>
+        </tr>
+        <tr>
+            <th scope="row">8</th>
+            <td>关闭订单</td>
+            <td><a href="https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_6.shtml" target="_blank">https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}/close</a></td>
+            <td><a asp-controller="WeChatPayV3" asp-action="OutTradeNoClose">立即测试</a></td>
+        </tr>
+    </tbody>
+</table>

+ 33 - 0
samples/WebApplicationSample/Views/WeChatPayV3/OutTradeNoClose.cshtml

@@ -0,0 +1,33 @@
+@model WeChatPayOutTradeNoCloseViewModel
+@{
+    ViewData["Title"] = "关闭订单";
+}
+<nav aria-label="breadcrumb">
+    <ol class="breadcrumb">
+        <li class="breadcrumb-item"><a asp-controller="WeChatPayV3" asp-action="Index">微信支付V3</a></li>
+        <li class="breadcrumb-item active" aria-current="page">@ViewData["Title"]</li>
+    </ol>
+</nav>
+<br />
+<div class="card">
+    <div class="card-body">
+        <form asp-controller="WeChatPayV3" asp-action="OutTradeNoClose">
+            <div asp-validation-summary="All" class="text-danger"></div>
+            <div class="form-group">
+                <label asp-for="OutTradeNo"></label>
+                <input type="text" class="form-control" asp-for="OutTradeNo" />
+            </div>
+            <button type="submit" class="btn btn-primary">提交请求</button>
+        </form>
+        <hr />
+        <form class="form-horizontal">
+            <div class="form-group">
+                <label>Response:</label>
+                <textarea class="form-control" rows="10">@ViewData["response"]</textarea>
+            </div>
+        </form>
+    </div>
+</div>
+@section Scripts {
+    @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
+}

+ 53 - 0
samples/WebApplicationSample/Views/WeChatPayV3/PubPay.cshtml

@@ -0,0 +1,53 @@
+@model WeChatPayPubPayV3ViewModel
+@{
+    ViewData["Title"] = "公众号支付-JSAPI下单";
+}
+<nav aria-label="breadcrumb">
+    <ol class="breadcrumb">
+        <li class="breadcrumb-item"><a asp-controller="WeChatPayV3" asp-action="Index">微信支付V3</a></li>
+        <li class="breadcrumb-item active" aria-current="page">@ViewData["Title"]</li>
+    </ol>
+</nav>
+<br />
+<div class="card">
+    <div class="card-body">
+        <form asp-controller="WeChatPayV3" asp-action="PubPay">
+            <div asp-validation-summary="All" class="text-danger"></div>
+            <div class="form-group">
+                <label asp-for="OutTradeNo"></label>
+                <input type="text" class="form-control" asp-for="OutTradeNo" value="@DateTime.Now.ToString("yyyyMMddHHmmssfff")">
+            </div>
+            <div class="form-group">
+                <label asp-for="Description"></label>
+                <input type="text" class="form-control" asp-for="Description" value="微信公众号支付测试">
+            </div>
+            <div class="form-group">
+                <label asp-for="Total"></label>
+                <input type="text" class="form-control" asp-for="Total" value="1">
+            </div>
+            <div class="form-group">
+                <label asp-for="NotifyUrl"></label>
+                <input type="text" class="form-control" asp-for="NotifyUrl" value="http://*/notify/wechatpay/v3/transactions">
+            </div>
+            <div class="form-group">
+                <label asp-for="OpenId"></label>
+                <input type="text" class="form-control" asp-for="OpenId">
+            </div>
+            <button type="submit" class="btn btn-primary">提交请求</button>
+        </form>
+        <hr />
+        <form class="form-horizontal">
+            <div class="form-group">
+                <label>Response:</label>
+                <textarea class="form-control" rows="10">@ViewData["response"]</textarea>
+            </div>
+            <div class="form-group">
+                <label>Parameter:</label>
+                <textarea class="form-control" rows="3">@ViewData["parameter"]</textarea>
+            </div>
+        </form>
+    </div>
+</div>
+@section Scripts {
+    @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
+}

+ 52 - 0
samples/WebApplicationSample/Views/WeChatPayV3/QrCodePay.cshtml

@@ -0,0 +1,52 @@
+@model WeChatPayQrCodePayV3ViewModel
+@{
+    ViewData["Title"] = "扫码支付-Native下单";
+}
+<nav aria-label="breadcrumb">
+    <ol class="breadcrumb">
+        <li class="breadcrumb-item"><a asp-controller="WeChatPayV3" asp-action="Index">微信支付V3</a></li>
+        <li class="breadcrumb-item active" aria-current="page">@ViewData["Title"]</li>
+    </ol>
+</nav>
+<br />
+<div class="card">
+    <div class="card-body">
+        <form asp-controller="WeChatPayV3" asp-action="QrCodePay">
+            <div asp-validation-summary="All" class="text-danger"></div>
+            <div class="form-group">
+                <label asp-for="OutTradeNo"></label>
+                <input type="text" class="form-control" asp-for="OutTradeNo" value="@DateTime.Now.ToString("yyyyMMddHHmmssfff")">
+            </div>
+            <div class="form-group">
+                <label asp-for="Description"></label>
+                <input type="text" class="form-control" asp-for="Description" value="微信扫码支付测试">
+            </div>
+            <div class="form-group">
+                <label asp-for="Total"></label>
+                <input type="text" class="form-control" asp-for="Total" value="1">
+            </div>
+            <div class="form-group">
+                <label asp-for="NotifyUrl"></label>
+                <input type="text" class="form-control" asp-for="NotifyUrl" value="http://*/notify/wechatpay/v3/transactions">
+            </div>
+            <button type="submit" class="btn btn-primary">提交请求</button>
+        </form>
+        <hr />
+        <form class="form-horizontal">
+            <div class="form-group">
+                <label>QrCode:</label>
+                @if (!string.IsNullOrEmpty(ViewData["qrcode"] as string))
+                {
+                    <embed src="../Home/QrCode?size=168&data=@ViewData["qrcode"]" width="168" height="168" type="image/svg+xml" />
+                }
+            </div>
+            <div class="form-group">
+                <label>Response:</label>
+                <textarea class="form-control" rows="10">@ViewData["response"]</textarea>
+            </div>
+        </form>
+    </div>
+</div>
+@section Scripts {
+    @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
+}

+ 33 - 0
samples/WebApplicationSample/Views/WeChatPayV3/QueryByOutTradeNo.cshtml

@@ -0,0 +1,33 @@
+@model WeChatPayQueryByOutTradeNoViewModel
+@{
+    ViewData["Title"] = "查询订单-商户订单号查询";
+}
+<nav aria-label="breadcrumb">
+    <ol class="breadcrumb">
+        <li class="breadcrumb-item"><a asp-controller="WeChatPayV3" asp-action="Index">微信支付V3</a></li>
+        <li class="breadcrumb-item active" aria-current="page">@ViewData["Title"]</li>
+    </ol>
+</nav>
+<br />
+<div class="card">
+    <div class="card-body">
+        <form asp-controller="WeChatPayV3" asp-action="QueryByOutTradeNo">
+            <div asp-validation-summary="All" class="text-danger"></div>
+            <div class="form-group">
+                <label asp-for="OutTradeNo"></label>
+                <input type="text" class="form-control" asp-for="OutTradeNo" />
+            </div>
+            <button type="submit" class="btn btn-primary">提交请求</button>
+        </form>
+        <hr />
+        <form class="form-horizontal">
+            <div class="form-group">
+                <label>Response:</label>
+                <textarea class="form-control" rows="10">@ViewData["response"]</textarea>
+            </div>
+        </form>
+    </div>
+</div>
+@section Scripts {
+    @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
+}

+ 33 - 0
samples/WebApplicationSample/Views/WeChatPayV3/QueryByTransactionId.cshtml

@@ -0,0 +1,33 @@
+@model WeChatPayQueryByTransactionIdViewModel
+@{
+    ViewData["Title"] = "查询订单-微信支付订单号查询";
+}
+<nav aria-label="breadcrumb">
+    <ol class="breadcrumb">
+        <li class="breadcrumb-item"><a asp-controller="WeChatPayV3" asp-action="Index">微信支付V3</a></li>
+        <li class="breadcrumb-item active" aria-current="page">@ViewData["Title"]</li>
+    </ol>
+</nav>
+<br />
+<div class="card">
+    <div class="card-body">
+        <form asp-controller="WeChatPayV3" asp-action="QueryByTransactionId">
+            <div asp-validation-summary="All" class="text-danger"></div>
+            <div class="form-group">
+                <label asp-for="TransactionId"></label>
+                <input type="text" class="form-control" asp-for="TransactionId" />
+            </div>
+            <button type="submit" class="btn btn-primary">提交请求</button>
+        </form>
+        <hr />
+        <form class="form-horizontal">
+            <div class="form-group">
+                <label>Response:</label>
+                <textarea class="form-control" rows="10">@ViewData["response"]</textarea>
+            </div>
+        </form>
+    </div>
+</div>
+@section Scripts {
+    @{await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
+}

+ 5 - 1
samples/WebApplicationSample/appsettings.Development.json

@@ -25,8 +25,12 @@
     // 为微信支付商户平台的API密钥,请注意不是APIv3密钥
     "Key": "",
 
+    // APIv3密钥
+    // 为微信支付商户平台的APIv3密钥,请注意不是API密钥,v3接口必填
+    "V3Key": "",
+
     // API证书(.p12扩展名)
-    // 为微信支付商户平台的API证书
+    // 为微信支付商户平台的API证书,v3接口必填
     // 可为证书文件路径 / 证书文件的base64字符串
     "Certificate": "",
 

+ 5 - 1
samples/WebApplicationSample/appsettings.json

@@ -26,8 +26,12 @@
     // 为微信支付商户平台的API密钥,请注意不是APIv3密钥
     "Key": "",
 
+    // APIv3密钥
+    // 为微信支付商户平台的APIv3密钥,请注意不是API密钥,v3接口必填
+    "V3Key": "",
+
     // API证书(.p12扩展名)
-    // 为微信支付商户平台的API证书
+    // 为微信支付商户平台的API证书,v3接口必填
     // 可为证书文件路径 / 证书文件的base64字符串
     "Certificate": "",
 

+ 44 - 0
src/Essensoft.AspNetCore.Payment.Security/AEAD_AES_256_GCM.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace Essensoft.AspNetCore.Payment.Security
+{
+    public class AEAD_AES_256_GCM
+    {
+        public static string Decrypt(string nonce, string ciphertext, string associatedData, string key)
+        {
+            if (string.IsNullOrEmpty(nonce))
+            {
+                throw new ArgumentNullException(nameof(nonce));
+            }
+
+            if (string.IsNullOrEmpty(ciphertext))
+            {
+                throw new ArgumentNullException(nameof(ciphertext));
+            }
+
+            if (string.IsNullOrEmpty(associatedData))
+            {
+                throw new ArgumentNullException(nameof(associatedData));
+            }
+
+            if (string.IsNullOrEmpty(key))
+            {
+                throw new ArgumentNullException(nameof(key));
+            }
+
+            using (var aesGcm = new AesGcm(Encoding.UTF8.GetBytes(key)))
+            {
+                var nonceBytes = Encoding.UTF8.GetBytes(nonce);
+                var ciphertextWithTagBytes = Convert.FromBase64String(ciphertext); // ciphertext 实际包含了 tag,即尾部16字节
+                var ciphertextBytes = ciphertextWithTagBytes[0..^16]; // 排除尾部16字节
+                var tagBytes = ciphertextWithTagBytes[^16..]; // 获取尾部16字节
+                var plaintextBytes = new byte[ciphertextBytes.Length];
+                var associatedDataBytes = Encoding.UTF8.GetBytes(associatedData);
+                aesGcm.Decrypt(nonceBytes, ciphertextBytes, tagBytes, plaintextBytes, associatedDataBytes);
+                return Encoding.UTF8.GetString(plaintextBytes);
+            }
+        }
+    }
+}

+ 26 - 0
src/Essensoft.AspNetCore.Payment.Security/SHA256WithRSA.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Security.Cryptography;
+using System.Text;
 
 namespace Essensoft.AspNetCore.Payment.Security
 {
@@ -57,5 +58,30 @@ namespace Essensoft.AspNetCore.Payment.Security
                 return rsa.VerifyData(InternalEncoding.GetEncoding(charset).GetBytes(data), Convert.FromBase64String(sign), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
             }
         }
+
+        public static string Sign(this RSA rsa, string data)
+        {
+            if (string.IsNullOrEmpty(data))
+            {
+                throw new ArgumentNullException(nameof(data));
+            }
+
+            return Convert.ToBase64String(rsa.SignData(Encoding.UTF8.GetBytes(data), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
+        }
+
+        public static bool Verify(this RSA rsa, string data, string sign)
+        {
+            if (string.IsNullOrEmpty(data))
+            {
+                throw new ArgumentNullException(nameof(data));
+            }
+
+            if (string.IsNullOrEmpty(sign))
+            {
+                throw new ArgumentNullException(nameof(sign));
+            }
+
+            return rsa.VerifyData(Encoding.UTF8.GetBytes(data), Convert.FromBase64String(sign), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
+        }
     }
 }

+ 26 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/Amount.cs

@@ -0,0 +1,26 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// 订单金额信息
+    /// </summary>    
+    public class Amount : WeChatPayObject
+    {
+        /// <summary>
+        /// 订单金额
+        /// 订单总金额,单位为分。
+        /// 示例值:100
+        /// </summary>
+        [JsonPropertyName("total")]
+        public int Total { get; set; }
+
+        /// <summary>
+        /// 货币类型	
+        /// CNY:人民币,境内商户号仅支持人民币。
+        /// 示例值:CNY
+        /// </summary>
+        [JsonPropertyName("currency")]
+        public string Currency { get; set; }
+    }
+}

+ 34 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/Certificate.cs

@@ -0,0 +1,34 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// 平台证书信息
+    /// </summary>
+    public class Certificate : WeChatPayObject
+    {
+        /// <summary>
+        /// 序列号
+        /// </summary>
+        [JsonPropertyName("serial_no")]
+        public string SerialNo { get; set; }
+
+        /// <summary>
+        /// 生效时间
+        /// </summary>
+        [JsonPropertyName("effective_time")]
+        public string EffectiveTime { get; set; }
+
+        /// <summary>
+        /// 失效时间
+        /// </summary>
+        [JsonPropertyName("expire_time")]
+        public string ExpireTime { get; set; }
+
+        /// <summary>
+        /// 加密证书信息
+        /// </summary>
+        [JsonPropertyName("encrypt_certificate")]
+        public EncryptCertificate EncryptCertificate { get; set; }
+    }
+}

+ 1 - 1
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/CouponInfo.cs

@@ -5,7 +5,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
     /// <summary>
     /// 代金券或立减优惠信息
     /// </summary>
-    public class CouponInfo
+    public class CouponInfo : WeChatPayObject
     {
         /// <summary>
         /// 代金券类型

+ 1 - 1
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/CouponRefundInfo.cs

@@ -5,7 +5,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
     /// <summary>
     /// 退款代金券信息
     /// </summary>
-    public class CouponRefundInfo
+    public class CouponRefundInfo : WeChatPayObject
     {
         /// <summary>
         /// 代金券类型

+ 37 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/Detail.cs

@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// 优惠功能
+    /// </summary>    
+    public class Detail : WeChatPayObject
+    {
+        /// <summary>
+        /// 订单原价
+        /// 1、商户侧一张小票订单可能被分多次支付,订单原价用于记录整张小票的交易金额。
+        /// 2、当订单原价与支付金额不相等,则不享受优惠。
+        /// 3、该字段主要用于防止同一张小票分多次支付,以享受多次优惠的情况,正常支付订单不必上传此参数。
+        /// 示例值:608800
+        /// </summary>
+        [JsonPropertyName("cost_price")]
+        public int CostPrice { get; set; }
+
+        /// <summary>
+        /// 商品小票ID
+        /// 商家小票ID
+        /// 示例值:微信123
+        /// </summary>
+        [JsonPropertyName("invoice_id")]
+        public string InvoiceId { get; set; }
+
+        /// <summary>
+        /// 单品列表
+        /// 单品列表信息
+        /// 条目个数限制:【1,undefined】
+        /// </summary>
+        [JsonPropertyName("goods_detail")]
+        public List<GoodsDetail> GoodsDetail { get; set; }
+    }
+}

+ 34 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/EncryptCertificate.cs

@@ -0,0 +1,34 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// 加密证书信息
+    /// </summary>
+    public class EncryptCertificate : WeChatPayObject
+    {
+        /// <summary>
+        /// 加密算法
+        /// </summary>
+        [JsonPropertyName("algorithm")]
+        public string Algorithm { get; set; }
+
+        /// <summary>
+        /// 加密使用的随机串初始化向量
+        /// </summary>
+        [JsonPropertyName("nonce")]
+        public string Nonce { get; set; }
+
+        /// <summary>
+        /// 附加数据包
+        /// </summary>
+        [JsonPropertyName("associated_data")]
+        public string AssociatedData { get; set; }
+
+        /// <summary>
+        /// Base64编码后的密文
+        /// </summary>
+        [JsonPropertyName("ciphertext")]
+        public string Ciphertext { get; set; }
+    }
+}

+ 16 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/ErrorDetail.cs

@@ -0,0 +1,16 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// 错误详情
+    /// </summary>    
+    public class ErrorDetail : WeChatPayObject
+    {
+        [JsonPropertyName("location")]
+        public string Location { get; set; }
+
+        [JsonPropertyName("value")]
+        public int Value { get; set; }
+    }
+}

+ 50 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/GoodsDetail.cs

@@ -0,0 +1,50 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// 单品列表
+    /// </summary>   
+    public class GoodsDetail : WeChatPayObject
+    {
+        /// <summary>
+        /// 商户侧商品编码
+        /// 由半角的大小写字母、数字、中划线、下划线中的一种或几种组成。
+        /// 示例值:商品编码
+        /// </summary>
+        [JsonPropertyName("merchant_goods_id")]
+        public string MerchantGoodsId { get; set; }
+
+        /// <summary>
+        /// 微信侧商品编码
+        /// 微信支付定义的统一商品编号(没有可不传)
+        /// 示例值:1001
+        /// </summary>
+        [JsonPropertyName("wechatpay_goods_id")]
+        public string WeChatPayGoodsId { get; set; }
+
+        /// <summary>
+        /// 商品名称
+        /// 商品的实际名称
+        /// 示例值:iPhoneX 256G
+        /// </summary>
+        [JsonPropertyName("goods_name")]
+        public string GoodsName { get; set; }
+
+        /// <summary>
+        /// 商品数量
+        /// 用户购买的数量
+        /// 示例值:1
+        /// </summary>
+        [JsonPropertyName("quantity")]
+        public int Quantity { get; set; }
+
+        /// <summary>
+        /// 商品单价
+        /// 商品单价,单位为分
+        /// 示例值:828800
+        /// </summary>
+        [JsonPropertyName("unit_price")]
+        public int UnitPrice { get; set; }
+    }
+}

+ 1 - 1
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/HBInfo.cs

@@ -5,7 +5,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
     /// <summary>
     /// 红包信息
     /// </summary>
-    public class HbInfo
+    public class HbInfo : WeChatPayObject
     {
         /// <summary>
         /// 领取红包的OpenId

+ 50 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/NotifyCiphertext.cs

@@ -0,0 +1,50 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// 通知报文
+    /// </summary>
+    public class NotifyCiphertext : WeChatPayObject
+    {
+        /// <summary>
+        /// 通知ID
+        /// 通知的唯一ID
+        /// 示例值:EV-2018022511223320873
+        /// </summary>
+        [JsonPropertyName("id")]
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 通知创建时间
+        /// 通知创建的时间,格式为yyyyMMddHHmmss
+        /// 示例值:20180225112233
+        /// </summary>
+        [JsonPropertyName("create_time")]
+        public string CreateTime { get; set; }
+
+        /// <summary>
+        /// 通知类型
+        /// 通知的类型,支付成功通知的类型为TRANSACTION.SUCCESS
+        /// 示例值:TRANSACTION.SUCCESS
+        /// </summary>
+        [JsonPropertyName("event_type")]
+        public string EventType { get; set; }
+
+        /// <summary>
+        /// 通知数据类型
+        /// 通知的资源数据类型,支付成功通知为encrypt-resource
+        /// 示例值:encrypt-resource
+        /// </summary>
+        [JsonPropertyName("resource_type")]
+        public string ResourceType { get; set; }
+
+        /// <summary>
+        /// 通知数据
+        /// 通知资源数据
+        /// json格式,见示例
+        /// </summary>
+        [JsonPropertyName("resource")]
+        public Resource Resource { get; set; }
+    }
+}

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

@@ -0,0 +1,18 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// 支付者信息
+    /// </summary>
+    public class Payer : WeChatPayObject
+    {
+        /// <summary>
+        /// 用户标识
+        /// 用户在直连商户appid下的唯一标识。
+        /// 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+        /// </summary>
+        [JsonPropertyName("openid")]
+        public string OpenId { get; set; }
+    }
+}

+ 100 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/PromotionDetail.cs

@@ -0,0 +1,100 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// 优惠功能
+    /// </summary>
+    public class PromotionDetail : WeChatPayObject
+    {
+        /// <summary>
+        /// 券ID
+        /// 券ID
+        /// 示例值:109519
+        /// </summary>
+        [JsonPropertyName("coupon_id")]
+        public string CouponId { get; set; }
+
+        /// <summary>
+        /// 优惠名称
+        /// 优惠名称
+        /// 示例值:单品惠-6
+        /// </summary>
+        [JsonPropertyName("name")]
+        public string Name { get; set; }
+
+        /// <summary>
+        /// 优惠范围
+        /// GLOBAL:全场代金券
+        /// SINGLE:单品优惠
+        /// 示例值:GLOBAL
+        /// </summary>
+        [JsonPropertyName("scope")]
+        public string Scope { get; set; }
+
+        /// <summary>
+        /// 优惠类型
+        /// CASH:充值
+        /// NOCASH:预充值
+        /// 示例值:CASH
+        /// </summary>
+        [JsonPropertyName("type")]
+        public string Type { get; set; }
+
+        /// <summary>
+        /// 优惠券面额
+        /// 优惠券面额
+        /// 示例值:100
+        /// </summary>
+        [JsonPropertyName("amount")]
+        public int Amount { get; set; }
+
+        /// <summary>
+        /// 活动ID
+        /// 活动ID
+        /// 示例值:931386
+        /// </summary>
+        [JsonPropertyName("stock_id")]
+        public string StockId { get; set; }
+
+        /// <summary>
+        /// 微信出资
+        /// 微信出资,单位为分
+        /// 示例值:0
+        /// </summary>
+        [JsonPropertyName("wechatpay_contribute")]
+        public int WeChatPayContribute { get; set; }
+
+        /// <summary>
+        /// 商户出资	
+        /// 商户出资,单位为分
+        /// 示例值:0
+        /// </summary>
+        [JsonPropertyName("merchant_contribute")]
+        public int MerchantContribute { get; set; }
+
+        /// <summary>
+        /// 其他出资
+        /// 其他出资,单位为分
+        /// 示例值:0
+        /// </summary>
+        [JsonPropertyName("other_contribute")]
+        public int OtherContribute { get; set; }
+
+        /// <summary>
+        /// 优惠币种
+        /// CNY:人民币,境内商户号仅支持人民币。
+        /// 示例值:CNY
+        /// </summary>
+        [JsonPropertyName("currency")]
+        public string Currency { get; set; }
+
+        /// <summary>
+        /// 单品列表
+        /// 单品列表信息
+        /// </summary>
+        [JsonPropertyName("goods_detail")]
+        public List<PromotionGoodsDetail> goods_detail { get; set; }
+    }
+}

+ 50 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/PromotionGoodsDetail.cs

@@ -0,0 +1,50 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// 单品列表信息
+    /// </summary>    
+    public class PromotionGoodsDetail : WeChatPayObject
+    {
+        /// <summary>
+        /// 商品编码
+        /// 商品编码
+        /// 示例值:M1006
+        /// </summary>
+        [JsonPropertyName("goods_id")]
+        public string GoodsId { get; set; }
+
+        /// <summary>
+        /// 商品数量
+        /// 用户购买的数量
+        /// 示例值:1
+        /// </summary>
+        [JsonPropertyName("quantity")]
+        public int Quantity { get; set; }
+
+        /// <summary>
+        /// 商品单价
+        /// 商品单价,单位为分
+        /// 示例值:100
+        /// </summary>
+        [JsonPropertyName("unit_price")]
+        public int UnitPrice { get; set; }
+
+        /// <summary>
+        /// 商品优惠金额
+        /// 商品优惠金额
+        /// 示例值:0  
+        /// </summary>
+        [JsonPropertyName("discount_amount")]
+        public int DiscountAmount { get; set; }
+
+        /// <summary>
+        /// 商品备注
+        /// 商品备注信息
+        /// 示例值:商品备注信息
+        /// </summary>
+        [JsonPropertyName("goods_remark")]
+        public string GoodsRemark { get; set; }
+    }
+}

+ 42 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/QueryAmount.cs

@@ -0,0 +1,42 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// 订单金额信息
+    /// </summary>    
+    public class QueryAmount : WeChatPayObject
+    {
+        /// <summary>
+        /// 订单金额
+        /// 订单总金额,单位为分。
+        /// 示例值:100
+        /// </summary>
+        [JsonPropertyName("total")]
+        public int Total { get; set; }
+
+        /// <summary>
+        /// 用户支付金额
+        /// 用户支付金额,单位为分。
+        /// 示例值:100
+        /// </summary>
+        [JsonPropertyName("payer_total")]
+        public int PayerTotal { get; set; }
+
+        /// <summary>
+        /// 货币类型	
+        /// CNY:人民币,境内商户号仅支持人民币。
+        /// 示例值:CNY
+        /// </summary>
+        [JsonPropertyName("currency")]
+        public string Currency { get; set; }
+
+        /// <summary>
+        /// 用户支付币种	
+        /// 用户支付币种
+        /// 示例值:CNY
+        /// </summary>
+        [JsonPropertyName("payer_currency")]
+        public string PayerCurrency { get; set; }
+    }
+}

+ 45 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/Receiver.cs

@@ -0,0 +1,45 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// 分账接收方对象
+    /// </summary>    
+    public class Receiver : WeChatPayObject
+    {
+        /// <summary>
+        /// 分账接收方类型
+        /// MERCHANT_ID:商户ID
+        /// </summary>
+        [JsonPropertyName("type")]
+        public int Type { get; set; }
+
+        /// <summary>
+        /// 分账接收方帐号
+        /// 申请本功能商户号
+        /// </summary>
+        [JsonPropertyName("account")]
+        public string Account { get; set; }
+
+        /// <summary>
+        /// 分账动账金额
+        /// 分账动账金额,单位为分,只能为整数
+        /// </summary>
+        [JsonPropertyName("amount")]
+        public int Amount { get; set; }
+
+        /// <summary>
+        /// 分账/回退描述
+        /// 分账/回退描述
+        /// </summary>
+        [JsonPropertyName("description")]
+        public string Description { get; set; }
+
+        /// <summary>
+        /// 成功时间
+        /// 成功时间,Rfc3339标准
+        /// </summary>
+        [JsonPropertyName("success_time")]
+        public string SuccessTime { get; set; }
+    }
+}

+ 1 - 1
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/RefundInfo.cs

@@ -6,7 +6,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
     /// <summary>
     /// 退款信息
     /// </summary>
-    public class RefundInfo
+    public class RefundInfo : WeChatPayObject
     {
         /// <summary>
         /// 商户退款单号

+ 42 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/Resource.cs

@@ -0,0 +1,42 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// 通知数据
+    /// </summary>
+    public class Resource : WeChatPayObject
+    {
+        /// <summary>
+        /// 加密算法类型
+        /// 对开启结果数据进行加密的加密算法,目前只支持AEAD_AES_256_GCM
+        /// 示例值:AEAD_AES_256_GCM
+        /// </summary>
+        [JsonPropertyName("algorithm")]
+        public string Algorithm { get; set; }
+
+        /// <summary>
+        /// 数据密文
+        /// Base64编码后的开启/停用结果数据密文
+        /// 示例值:sadsadsadsad
+        /// </summary>
+        [JsonPropertyName("ciphertext")]
+        public string Ciphertext { get; set; }
+
+        /// <summary>
+        /// 附加数据
+        /// 附加数据
+        /// 示例值:fdasfwqewlkja484w
+        /// </summary>
+        [JsonPropertyName("associated_data")]
+        public string AssociatedData { get; set; }
+
+        /// <summary>
+        /// 随机串
+        /// 加密使用的随机串
+        /// 示例值:fdasflkja484w
+        /// </summary>
+        [JsonPropertyName("nonce")]
+        public string Nonce { get; set; }
+    }
+}

+ 33 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/SceneInfo.cs

@@ -0,0 +1,33 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// 支付场景描述
+    /// </summary>       
+    public class SceneInfo : WeChatPayObject
+    {
+        /// <summary>
+        /// 用户终端IP
+        /// 调用微信支付API的机器IP,支持IPv4和IPv4两种格式的IP地址。
+        /// 示例值:14.23.150.211
+        /// </summary>
+        [JsonPropertyName("payer_client_ip")]
+        public string PayerClientIp { get; set; }
+
+        /// <summary>
+        /// 商户端设备号
+        /// 商户端设备号(门店号或收银设备ID)。
+        /// 示例值:013467007045764
+        /// </summary>
+        [JsonPropertyName("device_id")]
+        public string DeviceId { get; set; }
+
+        /// <summary>
+        /// 商户门店信息
+        /// 商户门店信息
+        /// </summary>
+        [JsonPropertyName("store_info")]
+        public StoreInfo StoreInfo { get; set; }
+    }
+}

+ 42 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/StoreInfo.cs

@@ -0,0 +1,42 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// 商户门店信息
+    /// </summary>  
+    public class StoreInfo : WeChatPayObject
+    {
+        /// <summary>
+        /// 门店编号
+        /// 商户侧门店编号
+        /// 示例值:0001
+        /// </summary>
+        [JsonPropertyName("id")]
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 门店名称
+        /// 商户侧门店名称
+        /// 示例值:腾讯大厦分店
+        /// </summary>
+        [JsonPropertyName("name")]
+        public string Name { get; set; }
+
+        /// <summary>
+        /// 地区编码
+        /// 地区编码,详细请见省市区编号对照表。
+        /// 示例值:440305
+        /// </summary>
+        [JsonPropertyName("area_code")]
+        public string AreaCode { get; set; }
+
+        /// <summary>
+        /// 详细地址
+        /// 详细的商户门店地址
+        /// 示例值:广东省深圳市南山区科技中一道10000号
+        /// </summary>
+        [JsonPropertyName("address")]
+        public string Address { get; set; }
+    }
+}

+ 99 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/WeChatPayTransactionsAppModel.cs

@@ -0,0 +1,99 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// APP下单API-请求参数
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_1.shtml
+    /// </summary>
+    public class WeChatPayTransactionsAppModel : WeChatPayObject
+    {
+        /// <summary>
+        /// 公众号ID
+        /// 直连商户申请的公众号或移动应用appid。
+        /// 示例值:wxd678efh567hg6787
+        /// </summary>
+        [JsonPropertyName("appid")]
+        public string AppId { get; set; }
+
+        /// <summary>
+        /// 直连商户号
+        /// 直连商户的商户号,由微信支付生成并下发。
+        /// 示例值:1230000109
+        /// </summary>
+        [JsonPropertyName("mchid")]
+        public string MchId { get; set; }
+
+        /// <summary>
+        /// 商品描述
+        /// 商品描述
+        /// 示例值:Image形象店-深圳腾大-QQ公仔
+        /// </summary>
+        [JsonPropertyName("description")]
+        public string Description { get; set; }
+
+        /// <summary>
+        /// 商户订单号
+        /// 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】。
+        /// 特殊规则:最小字符长度为6
+        /// 示例值:1217752501201407033233368018
+        /// </summary>
+        [JsonPropertyName("out_trade_no")]
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 交易结束时间
+        /// 订单失效时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
+        /// 示例值:2018-06-08T10:34:56+08:00
+        /// </summary>
+        [JsonPropertyName("time_expire")]
+        public string TimeExpire { get; set; }
+
+        /// <summary>
+        /// 附加数据
+        /// 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
+        /// 示例值:自定义数据
+        /// </summary>
+        [JsonPropertyName("attach")]
+        public string Attach { get; set; }
+
+        /// <summary>
+        /// 通知地址
+        /// 通知URL必须为直接可访问的URL,不允许携带查询串。
+        /// 格式:URL
+        /// 示例值:https://www.weixin.qq.com/wxpay/pay.php
+        /// </summary>
+        [JsonPropertyName("notify_url")]
+        public string NotifyUrl { get; set; }
+
+        /// <summary>
+        /// 订单优惠标记
+        /// 订单优惠标记
+        /// 示例值:WXG
+        /// </summary>
+        [JsonPropertyName("goods_tag")]
+        public string GoodsTag { get; set; }
+
+        /// <summary>
+        /// 订单金额
+        /// 订单金额信息
+        /// </summary>
+        [JsonPropertyName("amount")]
+        public Amount Amount { get; set; }
+
+        /// <summary>
+        /// 优惠功能
+        /// 优惠功能
+        /// </summary>
+        [JsonPropertyName("detail")]
+        public Detail Detail { get; set; }
+
+        /// <summary>
+        /// 场景信息
+        /// 支付场景描述
+        /// </summary>
+        [JsonPropertyName("scene_info")]
+        public SceneInfo SceneInfo { get; set; }
+    }
+}

+ 99 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/WeChatPayTransactionsH5Model.cs

@@ -0,0 +1,99 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// H5下单API-请求参数
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_4.shtml
+    /// </summary>
+    public class WeChatPayTransactionsH5Model : WeChatPayObject
+    {
+        /// <summary>
+        /// 公众号ID
+        /// 直连商户申请的公众号或移动应用appid。
+        /// 示例值:wxd678efh567hg6787
+        /// </summary>
+        [JsonPropertyName("appid")]
+        public string AppId { get; set; }
+
+        /// <summary>
+        /// 直连商户号
+        /// 直连商户的商户号,由微信支付生成并下发。
+        /// 示例值:1230000109
+        /// </summary>
+        [JsonPropertyName("mchid")]
+        public string MchId { get; set; }
+
+        /// <summary>
+        /// 商品描述
+        /// 商品描述
+        /// 示例值:Image形象店-深圳腾大-QQ公仔
+        /// </summary>
+        [JsonPropertyName("description")]
+        public string Description { get; set; }
+
+        /// <summary>
+        /// 商户订单号
+        /// 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】。
+        /// 特殊规则:最小字符长度为6
+        /// 示例值:1217752501201407033233368018
+        /// </summary>
+        [JsonPropertyName("out_trade_no")]
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 交易结束时间
+        /// 订单失效时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
+        /// 示例值:2018-06-08T10:34:56+08:00
+        /// </summary>
+        [JsonPropertyName("time_expire")]
+        public string TimeExpire { get; set; }
+
+        /// <summary>
+        /// 附加数据
+        /// 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
+        /// 示例值:自定义数据
+        /// </summary>
+        [JsonPropertyName("attach")]
+        public string Attach { get; set; }
+
+        /// <summary>
+        /// 通知地址
+        /// 通知URL必须为直接可访问的URL,不允许携带查询串。
+        /// 格式:URL
+        /// 示例值:https://www.weixin.qq.com/wxpay/pay.php
+        /// </summary>
+        [JsonPropertyName("notify_url")]
+        public string NotifyUrl { get; set; }
+
+        /// <summary>
+        /// 订单优惠标记
+        /// 订单优惠标记
+        /// 示例值:WXG
+        /// </summary>
+        [JsonPropertyName("goods_tag")]
+        public string GoodsTag { get; set; }
+
+        /// <summary>
+        /// 订单金额
+        /// 订单金额信息
+        /// </summary>
+        [JsonPropertyName("amount")]
+        public Amount Amount { get; set; }
+
+        /// <summary>
+        /// 优惠功能
+        /// 优惠功能
+        /// </summary>
+        [JsonPropertyName("detail")]
+        public Detail Detail { get; set; }
+
+        /// <summary>
+        /// 场景信息
+        /// 支付场景描述
+        /// </summary>
+        [JsonPropertyName("scene_info")]
+        public SceneInfo SceneInfo { get; set; }
+    }
+}

+ 102 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/WeChatPayTransactionsJsApiModel.cs

@@ -0,0 +1,102 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// JSAPI下单API-请求参数
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_2.shtml
+    /// </summary>
+    public class WeChatPayTransactionsJsApiModel : WeChatPayObject
+    {
+        /// <summary>
+        /// 公众号ID
+        /// 直连商户申请的公众号或移动应用appid。
+        /// 示例值:wxd678efh567hg6787
+        /// </summary>
+        [JsonPropertyName("appid")]
+        public string AppId { get; set; }
+
+        /// <summary>
+        /// 直连商户号
+        /// 直连商户的商户号,由微信支付生成并下发。
+        /// 示例值:1230000109
+        /// </summary>
+        [JsonPropertyName("mchid")]
+        public string MchId { get; set; }
+
+        /// <summary>
+        /// 商品描述
+        /// 商品描述
+        /// 示例值:Image形象店-深圳腾大-QQ公仔
+        /// </summary>
+        [JsonPropertyName("description")]
+        public string Description { get; set; }
+
+        /// <summary>
+        /// 商户订单号
+        /// 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】。
+        /// 特殊规则:最小字符长度为6
+        /// 示例值:1217752501201407033233368018
+        /// </summary>
+        [JsonPropertyName("out_trade_no")]
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 交易结束时间
+        /// 订单失效时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
+        /// 示例值:2018-06-08T10:34:56+08:00
+        /// </summary>
+        [JsonPropertyName("time_expire")]
+        public string TimeExpire { get; set; }
+
+        /// <summary>
+        /// 附加数据
+        /// 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
+        /// 示例值:自定义数据
+        /// </summary>
+        [JsonPropertyName("attach")]
+        public string Attach { get; set; }
+
+        /// <summary>
+        /// 通知地址
+        /// 通知URL必须为直接可访问的URL,不允许携带查询串。
+        /// 格式:URL
+        /// 示例值:https://www.weixin.qq.com/wxpay/pay.php
+        /// </summary>
+        [JsonPropertyName("notify_url")]
+        public string NotifyUrl { get; set; }
+
+        /// <summary>
+        /// 订单优惠标记
+        /// 订单优惠标记
+        /// 示例值:WXG
+        /// </summary>
+        [JsonPropertyName("goods_tag")]
+        public string GoodsTag { get; set; }
+
+        /// <summary>
+        /// 订单金额
+        /// </summary>
+        [JsonPropertyName("amount")]
+        public Amount Amount { get; set; }
+
+        /// <summary>
+        /// 支付者
+        /// </summary>
+        [JsonPropertyName("payer")]
+        public Payer Payer { get; set; }
+
+        /// <summary>
+        /// 优惠功能
+        /// </summary>
+        [JsonPropertyName("detail")]
+        public Detail Detail { get; set; }
+
+        /// <summary>
+        /// 场景信息
+        /// </summary>
+        [JsonPropertyName("scene_info")]
+        public SceneInfo SceneInfo { get; set; }
+    }
+}

+ 99 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/WeChatPayTransactionsNativeModel.cs

@@ -0,0 +1,99 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// Native下单API-请求参数
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_3.shtml
+    /// </summary>
+    public class WeChatPayTransactionsNativeModel : WeChatPayObject
+    {
+        /// <summary>
+        /// 公众号ID
+        /// 直连商户申请的公众号或移动应用appid。
+        /// 示例值:wxd678efh567hg6787
+        /// </summary>
+        [JsonPropertyName("appid")]
+        public string AppId { get; set; }
+
+        /// <summary>
+        /// 直连商户号
+        /// 直连商户的商户号,由微信支付生成并下发。
+        /// 示例值:1230000109
+        /// </summary>
+        [JsonPropertyName("mchid")]
+        public string MchId { get; set; }
+
+        /// <summary>
+        /// 商品描述
+        /// 商品描述
+        /// 示例值:Image形象店-深圳腾大-QQ公仔
+        /// </summary>
+        [JsonPropertyName("description")]
+        public string Description { get; set; }
+
+        /// <summary>
+        /// 商户订单号
+        /// 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】。
+        /// 特殊规则:最小字符长度为6
+        /// 示例值:1217752501201407033233368018
+        /// </summary>
+        [JsonPropertyName("out_trade_no")]
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 交易结束时间
+        /// 订单失效时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
+        /// 示例值:2018-06-08T10:34:56+08:00
+        /// </summary>
+        [JsonPropertyName("time_expire")]
+        public string TimeExpire { get; set; }
+
+        /// <summary>
+        /// 附加数据
+        /// 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
+        /// 示例值:自定义数据
+        /// </summary>
+        [JsonPropertyName("attach")]
+        public string Attach { get; set; }
+
+        /// <summary>
+        /// 通知地址
+        /// 通知URL必须为直接可访问的URL,不允许携带查询串。
+        /// 格式:URL
+        /// 示例值:https://www.weixin.qq.com/wxpay/pay.php
+        /// </summary>
+        [JsonPropertyName("notify_url")]
+        public string NotifyUrl { get; set; }
+
+        /// <summary>
+        /// 订单优惠标记
+        /// 订单优惠标记
+        /// 示例值:WXG
+        /// </summary>
+        [JsonPropertyName("goods_tag")]
+        public string GoodsTag { get; set; }
+
+        /// <summary>
+        /// 订单金额
+        /// 订单金额信息
+        /// </summary>
+        [JsonPropertyName("amount")]
+        public Amount Amount { get; set; }
+
+        /// <summary>
+        /// 优惠功能
+        /// 优惠功能
+        /// </summary>
+        [JsonPropertyName("detail")]
+        public Detail Detail { get; set; }
+
+        /// <summary>
+        /// 场景信息
+        /// 支付场景描述
+        /// </summary>
+        [JsonPropertyName("scene_info")]
+        public SceneInfo SceneInfo { get; set; }
+    }
+}

+ 20 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Domain/WeChatPayTransactionsOutTradeNoCloseModel.cs

@@ -0,0 +1,20 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Domain
+{
+    /// <summary>
+    /// 关闭订单API-请求参数
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_6.shtml
+    /// </summary>
+    public class WeChatPayTransactionsOutTradeNoCloseModel : WeChatPayObject
+    {
+        /// <summary>
+        /// 直连商户号
+        /// 直连商户的商户号,由微信支付生成并下发。
+        /// 示例值:1230000109
+        /// </summary>
+        [JsonPropertyName("mchid")]
+        public string MchId { get; set; }
+    }
+}

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

@@ -7,6 +7,7 @@
   </PropertyGroup>
 
   <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.1'">
+    <PackageReference Include="System.Text.Json" Version="4.7.2" />
     <PackageReference Include="Microsoft.Extensions.Http" Version="3.1.5" />
   </ItemGroup>
 

+ 30 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayV3Client.cs

@@ -0,0 +1,30 @@
+using System.Threading.Tasks;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay
+{
+    public interface IWeChatPayV3Client
+    {
+        /// <summary>
+        /// 执行 WeChatPay V3 Sdk请求。
+        /// </summary>
+        /// <param name="request">具体的WeChatPay V3 Sdk请求</param>
+        /// <param name="options">配置选项</param>
+        Task<WeChatPayDictionary> ExecuteAsync(IWeChatPayV3SdkRequest request, WeChatPayOptions options);
+
+        /// <summary>
+        /// 执行 WeChatPay V3 Get请求。
+        /// </summary>
+        /// <param name="request">具体的WeChatPay V3 Get请求</param>
+        /// <param name="options">配置选项</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteAsync<T>(IWeChatPayV3GetRequest<T> request, WeChatPayOptions options) where T : WeChatPayV3Response;
+
+        /// <summary>
+        /// 执行 WeChatPay V3 Post请求。
+        /// </summary>
+        /// <param name="request">具体的WeChatPay V3 Post请求</param>
+        /// <param name="options">配置选项</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteAsync<T>(IWeChatPayV3PostRequest<T> request, WeChatPayOptions options) where T : WeChatPayV3Response;
+    }
+}

+ 10 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayV3GetRequest.cs

@@ -0,0 +1,10 @@
+namespace Essensoft.AspNetCore.Payment.WeChatPay
+{
+    public interface IWeChatPayV3GetRequest<T> where T : WeChatPayV3Response
+    {
+        /// <summary>
+        /// 请求URL
+        /// </summary>
+        string GetRequestUrl();
+    }
+}

+ 21 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayV3NotifyClient.cs

@@ -0,0 +1,21 @@
+#if NETCOREAPP3_1
+
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay
+{
+    public interface IWeChatPayV3NotifyClient
+    {
+        /// <summary>
+        /// 执行 WeChatPay V3通知请求解析
+        /// </summary>
+        /// <typeparam name="T">领域对象</typeparam>
+        /// <param name="request">控制器的请求</param>
+        /// <param name="options">配置选项</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteAsync<T>(HttpRequest request, WeChatPayOptions options) where T : WeChatPayV3Notify;
+    }
+}
+
+#endif

+ 21 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayV3PostRequest.cs

@@ -0,0 +1,21 @@
+namespace Essensoft.AspNetCore.Payment.WeChatPay
+{
+    public interface IWeChatPayV3PostRequest<T> where T : WeChatPayV3Response
+    {
+        /// <summary>
+        /// 请求URL
+        /// </summary>
+        string GetRequestUrl();
+
+        /// <summary>
+        /// 获取QueryModel
+        /// </summary>
+        WeChatPayObject GetQueryModel();
+
+        /// <summary>
+        /// 设置QueryModel
+        /// </summary>
+        /// <param name="queryModel"></param>
+        void SetQueryModel(WeChatPayObject queryModel);
+    }
+}

+ 22 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/IWeChatPayV3SdkRequest.cs

@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay
+{
+    public interface IWeChatPayV3SdkRequest
+    {
+        /// <summary>
+        /// 获取所有的Key-Value形式的文本请求参数字典。其中:
+        /// Key: 请求参数名
+        /// Value: 请求参数文本值
+        /// </summary>
+        /// <returns>文本请求参数字典</returns>
+        IDictionary<string, string> GetParameters();
+
+        /// <summary>
+        /// 请求参数处理器
+        /// </summary>
+        /// <param name="options"></param>
+        /// <param name="sortedTxtParams"></param>
+        void PrimaryHandler(WeChatPayOptions options, WeChatPayDictionary sortedTxtParams);
+    }
+}

+ 73 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Notify/WeChatPayProfitSharingNotify.cs

@@ -0,0 +1,73 @@
+#if NETCOREAPP3_1
+
+using System.Text.Json.Serialization;
+using Essensoft.AspNetCore.Payment.WeChatPay.Domain;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Notify
+{
+    /// <summary>
+    /// 分账动账通知 (普通商户 / 服务商)
+    /// https://pay.weixin.qq.com/wiki/doc/api/allocation_sl.php?chapter=25_9&index=9
+    /// https://pay.weixin.qq.com/wiki/doc/api/allocation.php?chapter=27_9&index=9
+    /// </summary>
+    public class WeChatPayProfitSharingNotify : WeChatPayV3Notify
+    {
+        /// <summary>
+        /// 服务商商户号
+        /// 服务商模式分账发起商户
+        /// </summary>
+        [JsonPropertyName("sp_mchid")]
+        public string SpMchId { get; set; }
+
+        /// <summary>
+        /// 子商户号
+        /// 服务商模式分账出资商户
+        /// </summary>
+        [JsonPropertyName("sub_mchid")]
+        public string SubMchid { get; set; }
+
+        /// <summary>
+        /// 直连商户号
+        /// 直连模式分账发起和出资商户
+        /// </summary>
+        [JsonPropertyName("mchid")]
+        public string MchId { get; set; }
+
+        /// <summary>
+        /// 微信订单号
+        /// 微信支付订单号
+        /// </summary>
+        [JsonPropertyName("transaction_id")]
+        public string TransactionId { get; set; }
+
+        /// <summary>
+        /// 微信分账/回退单号
+        /// 微信分账/回退单号
+        /// </summary>
+        [JsonPropertyName("order_id")]
+        public string OrderId { get; set; }
+
+        /// <summary>
+        /// 商户分账/回退单号
+        /// 分账方系统内部的分账/回退单号
+        /// </summary>
+        [JsonPropertyName("out_order_no")]
+        public string OutOrderNo { get; set; }
+
+        /// <summary>
+        /// 分账接收方
+        /// 分账接收方对象
+        /// </summary>
+        [JsonPropertyName("receiver")]
+        public Receiver Receiver { get; set; }
+
+        /// <summary>
+        /// 成功时间
+        /// 成功时间,Rfc3339标准
+        /// </summary>
+        [JsonPropertyName("success_time")]
+        public string SuccessTime { get; set; }
+    }
+}
+
+#endif

+ 140 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Notify/WeChatPayTransactionsNotify.cs

@@ -0,0 +1,140 @@
+#if NETCOREAPP3_1
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using Essensoft.AspNetCore.Payment.WeChatPay.Domain;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Notify
+{
+    /// <summary>
+    /// 支付通知API
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_11.shtml
+    /// </summary>
+    public class WeChatPayTransactionsNotify : WeChatPayV3Notify
+    {
+        /// <summary>
+        /// 公众号ID
+        /// 直连商户申请的公众号或移动应用appid。
+        /// 示例值:wxd678efh567hg6787
+        /// </summary>
+        [JsonPropertyName("appid")]
+        public string AppId { get; set; }
+
+        /// <summary>
+        /// 直连商户号
+        /// 直连商户的商户号,由微信支付生成并下发。
+        /// 示例值:1230000109
+        /// </summary>
+        [JsonPropertyName("mchid")]
+        public string MchId { get; set; }
+
+        /// <summary>
+        /// 商户订单号
+        /// 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】。
+        /// 特殊规则:最小字符长度为6
+        /// 示例值:1217752501201407033233368018
+        /// </summary>
+        [JsonPropertyName("out_trade_no")]
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 微信支付订单号
+        /// 微信支付系统生成的订单号。
+        /// 示例值:1217752501201407033233368018
+        /// </summary>
+        [JsonPropertyName("transaction_id")]
+        public string TransactionId { get; set; }
+
+        /// <summary>
+        /// 交易类型
+        /// 交易类型,枚举值:
+        /// JSAPI:公众号支付
+        /// NATIVE:扫码支付
+        /// APP:APP支付
+        /// MICROPAY:付款码支付
+        /// MWEB:H5支付
+        /// FACEPAY:刷脸支付
+        /// 示例值:MICROPAY
+        /// </summary>
+        [JsonPropertyName("trade_type")]
+        public string TradeType { get; set; }
+
+        /// <summary>
+        /// 交易状态
+        /// 交易状态,枚举值:
+        /// SUCCESS:支付成功
+        /// REFUND:转入退款
+        /// NOTPAY:未支付
+        /// CLOSED:已关闭
+        /// REVOKED:已撤销(付款码支付)
+        /// USERPAYING:用户支付中(付款码支付)
+        /// PAYERROR:支付失败(其他原因,如银行返回失败)
+        /// 示例值:SUCCESS
+        /// </summary>
+        [JsonPropertyName("trade_state")]
+        public string TradeState { get; set; }
+
+        /// <summary>
+        /// 交易状态描述
+        /// 交易状态描述
+        /// 示例值:支付失败,请重新下单支付
+        /// </summary>
+        [JsonPropertyName("trade_state_desc")]
+        public string TradeStateDesc { get; set; }
+
+        /// <summary>
+        /// 付款银行
+        /// 银行类型,采用字符串类型的银行标识。
+        /// 示例值:CMC
+        /// </summary>
+        [JsonPropertyName("bank_type")]
+        public string BankType { get; set; }
+
+        /// <summary>
+        /// 附加数据
+        /// 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
+        /// 示例值:自定义数据
+        /// </summary>
+        [JsonPropertyName("attach")]
+        public string Attach { get; set; }
+
+        /// <summary>
+        /// 支付完成时间
+        /// 支付完成时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
+        /// 示例值:2018-06-08T10:34:56+08:00
+        /// </summary>
+        [JsonPropertyName("success_time")]
+        public string SuccessTime { get; set; }
+
+        /// <summary>
+        /// 支付者
+        /// 示例值:见请求示例
+        /// </summary>
+        [JsonPropertyName("combine_payer_info")]
+        public Payer CombinePayerInfo { get; set; }
+
+        /// <summary>
+        /// 订单金额
+        /// 订单金额信息
+        /// </summary>
+        [JsonPropertyName("amount")]
+        public QueryAmount Amount { get; set; }
+
+        /// <summary>
+        /// 场景信息
+        /// 支付场景描述
+        /// </summary>
+        [JsonPropertyName("scene_info")]
+        public SceneInfo SceneInfo { get; set; }
+
+        /// <summary>
+        /// 优惠功能
+        /// 优惠功能,享受优惠时返回该字段。
+        /// </summary>
+        [JsonPropertyName("promotion_detail")]
+        public List<PromotionDetail> PromotionDetail { get; set; }
+    }
+}
+
+#endif

+ 13 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/IWeChatPayNotifyParser.cs

@@ -0,0 +1,13 @@
+#if NETCOREAPP3_1
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Parser
+{
+    public interface IWeChatPayNotifyParser<T> where T : WeChatPayNotify
+    {
+        T Parse(string body);
+
+        T Parse(string body, string data);
+    }
+}
+
+#endif

+ 7 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/IWeChatPayObjectJsonParser.cs

@@ -0,0 +1,7 @@
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Parser
+{
+    public interface IWeChatPayObjectJsonParser<T> where T : WeChatPayObject
+    {
+        T Parse(string body);
+    }
+}

+ 0 - 9
src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/IWeChatPayParser.cs

@@ -1,9 +0,0 @@
-namespace Essensoft.AspNetCore.Payment.WeChatPay.Parser
-{
-    public interface IWeChatPayParser<T> where T : WeChatPayObject
-    {
-        T Parse(string body);
-
-        T Parse(string body, string data);
-    }
-}

+ 7 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/IWeChatPayResponseParser.cs

@@ -0,0 +1,7 @@
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Parser
+{
+    public interface IWeChatPayResponseParser<T> where T : WeChatPayResponse
+    {
+        T Parse(string body);
+    }
+}

+ 13 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/IWeChatPayV3NotifyJsonParser.cs

@@ -0,0 +1,13 @@
+#if NETCOREAPP3_1
+
+using Essensoft.AspNetCore.Payment.WeChatPay.Domain;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Parser
+{
+    public interface IWeChatPayV3NotifyJsonParser<T> where T : WeChatPayV3Notify
+    {
+        T Parse(string body, string v3key);
+    }
+}
+
+#endif

+ 7 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/IWeChatPayV3ResponseJsonParser.cs

@@ -0,0 +1,7 @@
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Parser
+{
+    public interface IWeChatPayV3ResponseJsonParser<T> where T : WeChatPayV3Response
+    {
+        T Parse(string body, int statusCode);
+    }
+}

+ 6 - 2
src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/WeChatPayXmlParser.cs → src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/WeChatPayNotifyXmlParser.cs

@@ -1,4 +1,6 @@
-using System;
+#if NETCOREAPP3_1
+
+using System;
 using System.IO;
 using System.Text;
 using System.Xml.Linq;
@@ -6,7 +8,7 @@ using System.Xml.Serialization;
 
 namespace Essensoft.AspNetCore.Payment.WeChatPay.Parser
 {
-    public class WeChatPayXmlParser<T> : IWeChatPayParser<T> where T : WeChatPayObject
+    public class WeChatPayNotifyXmlParser<T> : IWeChatPayNotifyParser<T> where T : WeChatPayNotify
     {
         public T Parse(string body)
         {
@@ -85,3 +87,5 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay.Parser
         }
     }
 }
+
+#endif

+ 32 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/WeChatPayObjectJsonParser.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Text.Encodings.Web;
+using System.Text.Json;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Parser
+{
+    public class WeChatPayObjectJsonParser<T> : IWeChatPayObjectJsonParser<T> where T : WeChatPayObject
+    {
+        private static readonly JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions { IgnoreNullValues = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
+
+        public T Parse(string body)
+        {
+            T result = null;
+
+            try
+            {
+                if (body.StartsWith("{") && body.EndsWith("}"))
+                {
+                    result = JsonSerializer.Deserialize<T>(body, jsonSerializerOptions);
+                }
+            }
+            catch { }
+
+            if (result == null)
+            {
+                result = Activator.CreateInstance<T>();
+            }
+
+            return result;
+        }
+    }
+}

+ 42 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/WeChatPayResponseXmlParser.cs

@@ -0,0 +1,42 @@
+using System;
+using System.IO;
+using System.Xml.Linq;
+using System.Xml.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Parser
+{
+    public class WeChatPayResponseXmlParser<T> : IWeChatPayResponseParser<T> where T : WeChatPayResponse
+    {
+        public T Parse(string body)
+        {
+            T result = null;
+            var parameters = new WeChatPayDictionary();
+
+            try
+            {
+                var bodyDoc = XDocument.Parse(body).Element("xml");
+                foreach (var element in bodyDoc.Elements())
+                {
+                    parameters.Add(element.Name.LocalName, element.Value);
+                }
+
+                using (var sr = new StringReader(body))
+                {
+                    var xmldes = new XmlSerializer(typeof(T));
+                    result = (T)xmldes.Deserialize(sr);
+                }
+            }
+            catch { }
+
+            if (result == null)
+            {
+                result = Activator.CreateInstance<T>();
+            }
+
+            result.Body = body;
+            result.Parameters = parameters;
+            result.Execute();
+            return result;
+        }
+    }
+}

+ 60 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/WeChatPayV3NotifyJsonParser.cs

@@ -0,0 +1,60 @@
+#if NETCOREAPP3_1
+
+using System;
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using Essensoft.AspNetCore.Payment.Security;
+using Essensoft.AspNetCore.Payment.WeChatPay.Domain;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Parser
+{
+    public class WeChatPayV3NotifyJsonParser<T> : IWeChatPayV3NotifyJsonParser<T> where T : WeChatPayV3Notify
+    {
+        private static readonly JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions { IgnoreNullValues = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
+
+        public T Parse(string body, string v3key)
+        {
+            T result = null;
+            var notifyCiphertext = default(NotifyCiphertext);
+            var resourcePlaintext = string.Empty;
+
+            try
+            {
+                if (body.StartsWith("{") && body.EndsWith("}"))
+                {
+                    notifyCiphertext = JsonSerializer.Deserialize<NotifyCiphertext>(body, jsonSerializerOptions);
+                }
+            }
+            catch { }
+
+            switch (notifyCiphertext.Resource.Algorithm)
+            {
+                case nameof(AEAD_AES_256_GCM):
+                    {
+                        resourcePlaintext = AEAD_AES_256_GCM.Decrypt(notifyCiphertext.Resource.Nonce, notifyCiphertext.Resource.Ciphertext, notifyCiphertext.Resource.AssociatedData, v3key);
+                    }
+                    break;
+                default:
+                    throw new WeChatPayException("Unknown algorithm!");
+            }
+
+            try
+            {
+                result = JsonSerializer.Deserialize<T>(resourcePlaintext, jsonSerializerOptions);
+            }
+            catch { }
+
+            if (result == null)
+            {
+                result = Activator.CreateInstance<T>();
+            }
+
+            result.Body = body;
+            result.NotifyCiphertext = notifyCiphertext;
+            result.ResourcePlaintext = resourcePlaintext;
+            return result;
+        }
+    }
+}
+
+#endif

+ 34 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Parser/WeChatPayV3ResponseJsonParser.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Text.Encodings.Web;
+using System.Text.Json;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Parser
+{
+    public class WeChatPayV3ResponseJsonParser<T> : IWeChatPayV3ResponseJsonParser<T> where T : WeChatPayV3Response
+    {
+        private static readonly JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions { IgnoreNullValues = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
+
+        public T Parse(string body, int statusCode)
+        {
+            T result = null;
+
+            try
+            {
+                if (body.StartsWith("{") && body.EndsWith("}"))
+                {
+                    result = JsonSerializer.Deserialize<T>(body, jsonSerializerOptions);
+                }
+            }
+            catch { }
+
+            if (result == null)
+            {
+                result = Activator.CreateInstance<T>();
+            }
+
+            result.Body = body;
+            result.StatusCode = statusCode;
+            return result;
+        }
+    }
+}

+ 16 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayCertificatesRequest.cs

@@ -0,0 +1,16 @@
+using Essensoft.AspNetCore.Payment.WeChatPay.Response;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Request
+{
+    /// <summary>
+    /// 获取平台证书列表
+    /// https://wechatpay-api.gitbook.io/wechatpay-api-v3/jie-kou-wen-dang/ping-tai-zheng-shu
+    /// </summary>
+    public class WeChatPayCertificatesRequest : IWeChatPayV3GetRequest<WeChatPayCertificatesResponse>
+    {
+        public string GetRequestUrl()
+        {
+            return "https://api.mch.weixin.qq.com/v3/certificates";
+        }
+    }
+}

+ 29 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayTransactionsAppRequest.cs

@@ -0,0 +1,29 @@
+using Essensoft.AspNetCore.Payment.WeChatPay.Response;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Request
+{
+    /// <summary>
+    /// APP下单API
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_1.shtml
+    /// </summary>
+    public class WeChatPayTransactionsAppRequest : IWeChatPayV3PostRequest<WeChatPayTransactionsAppResponse>
+    {
+        private WeChatPayObject queryModel;
+
+        public string GetRequestUrl()
+        {
+            return "https://api.mch.weixin.qq.com/v3/pay/transactions/app";
+        }
+
+        public WeChatPayObject GetQueryModel()
+        {
+            return queryModel;
+        }
+
+        public void SetQueryModel(WeChatPayObject queryModel)
+        {
+            this.queryModel = queryModel;
+        }
+    }
+}

+ 29 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayTransactionsH5Request.cs

@@ -0,0 +1,29 @@
+using Essensoft.AspNetCore.Payment.WeChatPay.Response;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Request
+{
+    /// <summary>
+    /// H5下单API
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_4.shtml
+    /// </summary>
+    public class WeChatPayTransactionsH5Request : IWeChatPayV3PostRequest<WeChatPayTransactionsH5Response>
+    {
+        private WeChatPayObject queryModel;
+
+        public string GetRequestUrl()
+        {
+            return "https://api.mch.weixin.qq.com/v3/pay/transactions/h5";
+        }
+
+        public WeChatPayObject GetQueryModel()
+        {
+            return queryModel;
+        }
+
+        public void SetQueryModel(WeChatPayObject queryModel)
+        {
+            this.queryModel = queryModel;
+        }
+    }
+}

+ 31 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayTransactionsIdRequest.cs

@@ -0,0 +1,31 @@
+using Essensoft.AspNetCore.Payment.WeChatPay.Response;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Request
+{
+    /// <summary>
+    /// 查询订单API - 微信支付订单号查询
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_5.shtml
+    /// </summary>
+    public class WeChatPayTransactionsIdRequest : IWeChatPayV3GetRequest<WeChatPayTransactionsIdResponse>
+    {
+        /// <summary>
+        /// 直连商户号
+        /// 直连商户的商户号,由微信支付生成并下发。
+        /// 示例值:1230000109
+        /// </summary>
+        public string MchId { get; set; }
+
+        /// <summary>
+        /// 微信支付订单号
+        /// 微信支付系统生成的订单号
+        /// 示例值:1217752501201407033233368018
+        /// </summary>
+        public string TransactionId { get; set; }
+
+        public string GetRequestUrl()
+        {
+            return $"https://api.mch.weixin.qq.com/v3/pay/transactions/id/{TransactionId}?mchid={MchId}";
+        }
+    }
+}

+ 29 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayTransactionsJsApiRequest.cs

@@ -0,0 +1,29 @@
+using Essensoft.AspNetCore.Payment.WeChatPay.Response;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Request
+{
+    /// <summary>
+    /// JSAPI下单API
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_2.shtml
+    /// </summary>
+    public class WeChatPayTransactionsJsApiRequest : IWeChatPayV3PostRequest<WeChatPayTransactionsJsApiResponse>
+    {
+        private WeChatPayObject queryModel;
+
+        public string GetRequestUrl()
+        {
+            return "https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi";
+        }
+
+        public WeChatPayObject GetQueryModel()
+        {
+            return queryModel;
+        }
+
+        public void SetQueryModel(WeChatPayObject queryModel)
+        {
+            this.queryModel = queryModel;
+        }
+    }
+}

+ 29 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayTransactionsNativeRequest.cs

@@ -0,0 +1,29 @@
+using Essensoft.AspNetCore.Payment.WeChatPay.Response;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Request
+{
+    /// <summary>
+    /// Native下单API
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_3.shtml
+    /// </summary>
+    public class WeChatPayTransactionsNativeRequest : IWeChatPayV3PostRequest<WeChatPayTransactionsNativeResponse>
+    {
+        private WeChatPayObject queryModel;
+
+        public string GetRequestUrl()
+        {
+            return "https://api.mch.weixin.qq.com/v3/pay/transactions/native";
+        }
+
+        public WeChatPayObject GetQueryModel()
+        {
+            return queryModel;
+        }
+
+        public void SetQueryModel(WeChatPayObject queryModel)
+        {
+            this.queryModel = queryModel;
+        }
+    }
+}

+ 37 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayTransactionsOutTradeNoCloseRequest.cs

@@ -0,0 +1,37 @@
+using Essensoft.AspNetCore.Payment.WeChatPay.Response;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Request
+{
+    /// <summary>
+    /// 关闭订单API
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_6.shtml
+    /// </summary>
+    public class WeChatPayTransactionsOutTradeNoCloseRequest : IWeChatPayV3PostRequest<WeChatPayTransactionsOutTradeNoCloseResponse>
+    {
+        /// <summary>
+        /// 商户订单号
+        /// 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】。
+        /// 特殊规则:最小字符长度为6
+        /// 示例值:1217752501201407033233368018
+        /// </summary>
+        public string OutTradeNo { get; set; }
+
+        private WeChatPayObject queryModel;
+
+        public string GetRequestUrl()
+        {
+            return $"https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{OutTradeNo}/close";
+        }
+
+        public WeChatPayObject GetQueryModel()
+        {
+            return queryModel;
+        }
+
+        public void SetQueryModel(WeChatPayObject queryModel)
+        {
+            this.queryModel = queryModel;
+        }
+    }
+}

+ 32 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayTransactionsOutTradeNoRequest.cs

@@ -0,0 +1,32 @@
+using Essensoft.AspNetCore.Payment.WeChatPay.Response;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Request
+{
+    /// <summary>
+    /// 查询订单API - 商户订单号查询
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_5.shtml
+    /// </summary>
+    public class WeChatPayTransactionsOutTradeNoRequest : IWeChatPayV3GetRequest<WeChatPayTransactionsOutTradeNoResponse>
+    {
+        /// <summary>
+        /// 直连商户号
+        /// 直连商户的商户号,由微信支付生成并下发。
+        /// 示例值:1230000109
+        /// </summary>
+        public string MchId { get; set; }
+
+        /// <summary>
+        /// 商户订单号
+        /// 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】。
+        /// 特殊规则:最小字符长度为6
+        /// 示例值:1217752501201407033233368018
+        /// </summary>
+        public string OutTradeNo { get; set; }
+
+        public string GetRequestUrl()
+        {
+            return $"https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{OutTradeNo}?mchid={MchId}";
+        }
+    }
+}

+ 67 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayV3AppSdkRequest.cs

@@ -0,0 +1,67 @@
+using System.Collections.Generic;
+using Essensoft.AspNetCore.Payment.Security;
+using Essensoft.AspNetCore.Payment.WeChatPay.Utility;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Request
+{
+    /// <summary>
+    /// APP支付 调起支付
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_8.shtml
+    /// </summary>
+    public class WeChatPayV3AppSdkRequest : IWeChatPayV3SdkRequest
+    {
+        /// <summary>
+        /// 预支付交易会话ID
+        /// 微信返回的支付交易会话id。
+        /// 示例值: WX1217752501201407033233368018
+        /// </summary>
+        public string PrepayId { get; set; }
+
+        /// <summary>
+        /// 订单详情扩展字符串
+        /// 暂填写固定值Sign=WXPay
+        /// 示例值:Sign=WXPay
+        /// </summary>
+        public string Package { get; set; } = "Sign=WXPay";
+
+        #region IWeChatPayV3SdkRequest Members
+
+        public IDictionary<string, string> GetParameters()
+        {
+            var parameters = new WeChatPayDictionary
+            {
+                { "prepayid", PrepayId },
+                { "package", Package }
+            };
+            return parameters;
+        }
+
+        public void PrimaryHandler(WeChatPayOptions options, WeChatPayDictionary sortedTxtParams)
+        {
+            if (!string.IsNullOrEmpty(options.SubAppId) && !string.IsNullOrEmpty(options.SubMchId))
+            {
+                sortedTxtParams.Add(WeChatPayConsts.appid, options.SubAppId);
+                sortedTxtParams.Add(WeChatPayConsts.partnerid, options.SubMchId);
+            }
+            else
+            {
+                sortedTxtParams.Add(WeChatPayConsts.appid, options.AppId);
+                sortedTxtParams.Add(WeChatPayConsts.partnerid, options.MchId);
+            }
+
+            sortedTxtParams.Add(WeChatPayConsts.noncestr, WeChatPayUtility.GenerateNonceStr());
+            sortedTxtParams.Add(WeChatPayConsts.timestamp, WeChatPayUtility.GetTimeStamp());
+
+            var signatureSourceDate = BuildSignatureSourceDate(sortedTxtParams);
+            sortedTxtParams.Add(WeChatPayConsts.sign, options.CertificateRSAPrivateKey.Sign(signatureSourceDate));
+        }
+
+        private static string BuildSignatureSourceDate(WeChatPayDictionary sortedTxtParams)
+        {
+            return $"{sortedTxtParams.GetValue(WeChatPayConsts.appid)}\n{sortedTxtParams.GetValue(WeChatPayConsts.timestamp)}\n{sortedTxtParams.GetValue(WeChatPayConsts.noncestr)}\n{sortedTxtParams.GetValue(WeChatPayConsts.prepayid)}\n";
+        }
+
+        #endregion
+    }
+}

+ 56 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Request/WeChatPayV3JsApiSdkRequest.cs

@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+using Essensoft.AspNetCore.Payment.Security;
+using Essensoft.AspNetCore.Payment.WeChatPay.Utility;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Request
+{
+    /// <summary>
+    /// JS调起支付API
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_8.shtml
+    /// </summary>
+    public class WeChatPayV3JsApiSdkRequest : IWeChatPayV3SdkRequest
+    {
+        /// <summary>
+        /// 订单详情扩展字符串
+        /// </summary>
+        public string Package { get; set; }
+
+        #region IWeChatPayV3SdkRequest Members
+
+        public IDictionary<string, string> GetParameters()
+        {
+            var parameters = new WeChatPayDictionary
+            {
+                { "package", Package }
+            };
+            return parameters;
+        }
+
+        public void PrimaryHandler(WeChatPayOptions options, WeChatPayDictionary sortedTxtParams)
+        {
+            if (!string.IsNullOrEmpty(options.SubAppId))
+            {
+                sortedTxtParams.Add(WeChatPayConsts.appId, options.SubAppId);
+            }
+            else
+            {
+                sortedTxtParams.Add(WeChatPayConsts.appId, options.AppId);
+            }
+
+            sortedTxtParams.Add(WeChatPayConsts.timeStamp, WeChatPayUtility.GetTimeStamp());
+            sortedTxtParams.Add(WeChatPayConsts.nonceStr, WeChatPayUtility.GenerateNonceStr());
+            sortedTxtParams.Add(WeChatPayConsts.signType, WeChatPayConsts.RSA);
+
+            var signatureSourceDate = BuildSignatureSourceDate(sortedTxtParams);
+            sortedTxtParams.Add(WeChatPayConsts.paySign, options.CertificateRSAPrivateKey.Sign(signatureSourceDate));
+        }
+
+        private static string BuildSignatureSourceDate(WeChatPayDictionary sortedTxtParams)
+        {
+            return $"{sortedTxtParams.GetValue(WeChatPayConsts.appId)}\n{sortedTxtParams.GetValue(WeChatPayConsts.timeStamp)}\n{sortedTxtParams.GetValue(WeChatPayConsts.nonceStr)}\n{sortedTxtParams.GetValue(WeChatPayConsts.package)}\n";
+        }
+
+        #endregion
+    }
+}

+ 12 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Response/WeChatPayCertificatesResponse.cs

@@ -0,0 +1,12 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using Essensoft.AspNetCore.Payment.WeChatPay.Domain;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Response
+{
+    public class WeChatPayCertificatesResponse : WeChatPayV3Response
+    {
+        [JsonPropertyName("data")]
+        public List<Certificate> Certificates { get; set; }
+    }
+}

+ 20 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Response/WeChatPayTransactionsAppResponse.cs

@@ -0,0 +1,20 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Response
+{
+    /// <summary>
+    /// APP下单API-返回参数
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_1.shtml
+    /// </summary>
+    public class WeChatPayTransactionsAppResponse : WeChatPayV3Response
+    {
+        /// <summary>
+        /// 预支付交易会话标识
+        /// 预支付交易会话标识。
+        /// 示例值:wx201410272009395522657a690389285100
+        /// </summary>
+        [JsonPropertyName("prepay_id")]
+        public string PrepayId { get; set; }
+    }
+}

+ 20 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Response/WeChatPayTransactionsH5Response.cs

@@ -0,0 +1,20 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Response
+{
+    /// <summary>
+    /// H5下单API-返回参数
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_4.shtml
+    /// </summary>
+    public class WeChatPayTransactionsH5Response : WeChatPayV3Response
+    {
+        /// <summary>
+        /// 支付跳转链接
+        /// h5_url为拉起微信支付收银台的中间页面,可通过访问该url来拉起微信客户端,完成支付,h5_url的有效期为5分钟。
+        /// 示例值:https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx2016121516420242444321ca0631331346&package=1405458241
+        /// </summary>
+        [JsonPropertyName("h5_url")]
+        public string H5Url { get; set; }
+    }
+}

+ 136 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Response/WeChatPayTransactionsIdResponse.cs

@@ -0,0 +1,136 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using Essensoft.AspNetCore.Payment.WeChatPay.Domain;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Response
+{
+    /// <summary>
+    /// 查询订单API-微信支付订单号查询-返回参数
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_5.shtml
+    /// </summary>
+    public class WeChatPayTransactionsIdResponse : WeChatPayV3Response
+    {
+        /// <summary>
+        /// 公众号ID
+        /// 直连商户申请的公众号或移动应用appid。
+        /// 示例值:wxd678efh567hg6787
+        /// </summary>
+        [JsonPropertyName("appid")]
+        public string AppId { get; set; }
+
+        /// <summary>
+        /// 直连商户号
+        /// 直连商户的商户号,由微信支付生成并下发。
+        /// 示例值:1230000109
+        /// </summary>
+        [JsonPropertyName("mchid")]
+        public string MchId { get; set; }
+
+        /// <summary>
+        /// 商户订单号
+        /// 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】。
+        /// 特殊规则:最小字符长度为6
+        /// 示例值:1217752501201407033233368018
+        /// </summary>
+        [JsonPropertyName("out_trade_no")]
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 微信支付订单号
+        /// 微信支付系统生成的订单号。
+        /// 示例值:1217752501201407033233368018
+        /// </summary>
+        [JsonPropertyName("transaction_id")]
+        public string TransactionId { get; set; }
+
+        /// <summary>
+        /// 交易类型
+        /// 交易类型,枚举值:
+        /// JSAPI:公众号支付
+        /// NATIVE:扫码支付
+        /// APP:APP支付
+        /// MICROPAY:付款码支付
+        /// MWEB:H5支付
+        /// FACEPAY:刷脸支付
+        /// 示例值:MICROPAY
+        /// </summary>
+        [JsonPropertyName("trade_type")]
+        public string TradeType { get; set; }
+
+        /// <summary>
+        /// 交易状态
+        /// 交易状态,枚举值:
+        /// SUCCESS:支付成功
+        /// REFUND:转入退款
+        /// NOTPAY:未支付
+        /// CLOSED:已关闭
+        /// REVOKED:已撤销(付款码支付)
+        /// USERPAYING:用户支付中(付款码支付)
+        /// PAYERROR:支付失败(其他原因,如银行返回失败)
+        /// 示例值:SUCCESS
+        /// </summary>
+        [JsonPropertyName("trade_state")]
+        public string TradeState { get; set; }
+
+        /// <summary>
+        /// 交易状态描述
+        /// 交易状态描述
+        /// 示例值:支付失败,请重新下单支付
+        /// </summary>
+        [JsonPropertyName("trade_state_desc")]
+        public string TradeStateDesc { get; set; }
+
+        /// <summary>
+        /// 付款银行
+        /// 银行类型,采用字符串类型的银行标识。
+        /// 示例值:CMC
+        /// </summary>
+        [JsonPropertyName("bank_type")]
+        public string BankType { get; set; }
+
+        /// <summary>
+        /// 附加数据
+        /// 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
+        /// 示例值:自定义数据
+        /// </summary>
+        [JsonPropertyName("attach")]
+        public string Attach { get; set; }
+
+        /// <summary>
+        /// 支付完成时间
+        /// 支付完成时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
+        /// 示例值:2018-06-08T10:34:56+08:00
+        /// </summary>
+        [JsonPropertyName("success_time")]
+        public string SuccessTime { get; set; }
+
+        /// <summary>
+        /// 支付者
+        /// 示例值:见请求示例
+        /// </summary>
+        [JsonPropertyName("combine_payer_info")]
+        public Payer CombinePayerInfo { get; set; }
+
+        /// <summary>
+        /// 订单金额
+        /// 订单金额信息
+        /// </summary>
+        [JsonPropertyName("amount")]
+        public QueryAmount Amount { get; set; }
+
+        /// <summary>
+        /// 场景信息
+        /// 支付场景描述
+        /// </summary>
+        [JsonPropertyName("scene_info")]
+        public SceneInfo SceneInfo { get; set; }
+
+        /// <summary>
+        /// 优惠功能
+        /// 优惠功能,享受优惠时返回该字段。
+        /// </summary>
+        [JsonPropertyName("promotion_detail")]
+        public List<PromotionDetail> PromotionDetail { get; set; }
+    }
+}

+ 20 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Response/WeChatPayTransactionsJsApiResponse.cs

@@ -0,0 +1,20 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Response
+{
+    /// <summary>
+    /// JSAPI下单API-返回参数
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_2.shtml
+    /// </summary>
+    public class WeChatPayTransactionsJsApiResponse : WeChatPayV3Response
+    {
+        /// <summary>
+        /// 预支付交易会话标识	
+        /// 预支付交易会话标识。
+        /// 示例值:wx201410272009395522657a690389285100
+        /// </summary>
+        [JsonPropertyName("prepay_id")]
+        public string PrepayId { get; set; }
+    }
+}

+ 21 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Response/WeChatPayTransactionsNativeResponse.cs

@@ -0,0 +1,21 @@
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Response
+{
+    /// <summary>
+    /// Native下单API-返回参数
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_3.shtml
+    /// </summary>
+    public class WeChatPayTransactionsNativeResponse : WeChatPayV3Response
+    {
+        /// <summary>
+        /// 二维码链接
+        /// 此URL用于生成支付二维码,然后提供给用户扫码支付。
+        /// 注意:code_url并非固定值,使用时按照URL格式转成二维码即可。
+        /// 示例值:weixin://wxpay/bizpayurl/up?pr=NwY5Mz9&groupid=00
+        /// </summary>
+        [JsonPropertyName("code_url")]
+        public string CodeUrl { get; set; }
+    }
+}

+ 11 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Response/WeChatPayTransactionsOutTradeNoCloseResponse.cs

@@ -0,0 +1,11 @@
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Response
+{
+    /// <summary>
+    /// 关闭订单API-返回参数
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_6.shtml
+    /// </summary>
+    public class WeChatPayTransactionsOutTradeNoCloseResponse : WeChatPayV3Response
+    {
+    }
+}

+ 136 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/Response/WeChatPayTransactionsOutTradeNoResponse.cs

@@ -0,0 +1,136 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using Essensoft.AspNetCore.Payment.WeChatPay.Domain;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay.Response
+{
+    /// <summary>
+    /// 查询订单API-商户订单号查询-返回参数
+    /// 最新更新时间:2020.05.26
+    /// https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/transactions/chapter3_5.shtml
+    /// </summary>
+    public class WeChatPayTransactionsOutTradeNoResponse : WeChatPayV3Response
+    {
+        /// <summary>
+        /// 公众号ID
+        /// 直连商户申请的公众号或移动应用appid。
+        /// 示例值:wxd678efh567hg6787
+        /// </summary>
+        [JsonPropertyName("appid")]
+        public string AppId { get; set; }
+
+        /// <summary>
+        /// 直连商户号
+        /// 直连商户的商户号,由微信支付生成并下发。
+        /// 示例值:1230000109
+        /// </summary>
+        [JsonPropertyName("mchid")]
+        public string MchId { get; set; }
+
+        /// <summary>
+        /// 商户订单号
+        /// 商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】。
+        /// 特殊规则:最小字符长度为6
+        /// 示例值:1217752501201407033233368018
+        /// </summary>
+        [JsonPropertyName("out_trade_no")]
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 微信支付订单号
+        /// 微信支付系统生成的订单号。
+        /// 示例值:1217752501201407033233368018
+        /// </summary>
+        [JsonPropertyName("transaction_id")]
+        public string TransactionId { get; set; }
+
+        /// <summary>
+        /// 交易类型
+        /// 交易类型,枚举值:
+        /// JSAPI:公众号支付
+        /// NATIVE:扫码支付
+        /// APP:APP支付
+        /// MICROPAY:付款码支付
+        /// MWEB:H5支付
+        /// FACEPAY:刷脸支付
+        /// 示例值:MICROPAY
+        /// </summary>
+        [JsonPropertyName("trade_type")]
+        public string TradeType { get; set; }
+
+        /// <summary>
+        /// 交易状态
+        /// 交易状态,枚举值:
+        /// SUCCESS:支付成功
+        /// REFUND:转入退款
+        /// NOTPAY:未支付
+        /// CLOSED:已关闭
+        /// REVOKED:已撤销(付款码支付)
+        /// USERPAYING:用户支付中(付款码支付)
+        /// PAYERROR:支付失败(其他原因,如银行返回失败)
+        /// 示例值:SUCCESS
+        /// </summary>
+        [JsonPropertyName("trade_state")]
+        public string TradeState { get; set; }
+
+        /// <summary>
+        /// 交易状态描述
+        /// 交易状态描述
+        /// 示例值:支付失败,请重新下单支付
+        /// </summary>
+        [JsonPropertyName("trade_state_desc")]
+        public string TradeStateDesc { get; set; }
+
+        /// <summary>
+        /// 付款银行
+        /// 银行类型,采用字符串类型的银行标识。
+        /// 示例值:CMC
+        /// </summary>
+        [JsonPropertyName("bank_type")]
+        public string BankType { get; set; }
+
+        /// <summary>
+        /// 附加数据
+        /// 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
+        /// 示例值:自定义数据
+        /// </summary>
+        [JsonPropertyName("attach")]
+        public string Attach { get; set; }
+
+        /// <summary>
+        /// 支付完成时间
+        /// 支付完成时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
+        /// 示例值:2018-06-08T10:34:56+08:00
+        /// </summary>
+        [JsonPropertyName("success_time")]
+        public string SuccessTime { get; set; }
+
+        /// <summary>
+        /// 支付者
+        /// 示例值:见请求示例
+        /// </summary>
+        [JsonPropertyName("combine_payer_info")]
+        public Payer CombinePayerInfo { get; set; }
+
+        /// <summary>
+        /// 订单金额
+        /// 订单金额信息
+        /// </summary>
+        [JsonPropertyName("amount")]
+        public QueryAmount Amount { get; set; }
+
+        /// <summary>
+        /// 场景信息
+        /// 支付场景描述
+        /// </summary>
+        [JsonPropertyName("scene_info")]
+        public SceneInfo SceneInfo { get; set; }
+
+        /// <summary>
+        /// 优惠功能
+        /// 优惠功能,享受优惠时返回该字段。
+        /// </summary>
+        [JsonPropertyName("promotion_detail")]
+        public List<PromotionDetail> PromotionDetail { get; set; }
+    }
+}

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

@@ -12,11 +12,14 @@ namespace Microsoft.Extensions.DependencyInjection
 
             services.TryAddEnumerable(ServiceDescriptor.Singleton<IHttpMessageHandlerBuilderFilter, WeChatPayHandlerBuilderFilter>());
 
-            services.AddSingleton<WeChatPayCertificateManager>();
+            services.AddSingleton<WeChatPayClientCertificateManager>();
+            services.AddSingleton<WeChatPayPlatformCertificateManager>();
             services.AddSingleton<IWeChatPayClient, WeChatPayClient>();
+            services.AddSingleton<IWeChatPayV3Client, WeChatPayV3Client>();
 
 #if NETCOREAPP3_1
             services.AddSingleton<IWeChatPayNotifyClient, WeChatPayNotifyClient>();
+            services.AddSingleton<IWeChatPayV3NotifyClient, WeChatPayV3NotifyClient>();
 #endif
         }
     }

+ 101 - 10
src/Essensoft.AspNetCore.Payment.WeChatPay/Utility/HttpClientExtensions.cs

@@ -1,27 +1,118 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.Linq;
 using System.Net.Http;
+using System.Net.Http.Headers;
 using System.Text;
+using System.Text.Encodings.Web;
+using System.Text.Json;
 using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.Security;
 
 namespace Essensoft.AspNetCore.Payment.WeChatPay.Utility
 {
     public static class HttpClientExtensions
     {
-        /// <summary>
-        /// 执行HTTP POST请求。
-        /// </summary>
-        /// <param name="client">HttpClient</param>
-        /// <param name="url">请求地址</param>
-        /// <param name="textParams">请求参数</param>
-        /// <returns>HTTP响应内容</returns>
-        public static async Task<string> PostAsync(this HttpClient client, string url, IDictionary<string, string> textParams)
+        private static readonly JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions { IgnoreNullValues = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
+
+        public static async Task<string> PostAsync<T>(this HttpClient client, IWeChatPayRequest<T> request, IDictionary<string, string> textParams) where T : WeChatPayResponse
+        {
+            var url = request.GetRequestUrl();
+            var content = WeChatPayUtility.BuildContent(textParams);
+            using (var reqContent = new StringContent(content, Encoding.UTF8, "application/xml"))
+            using (var resp = await client.PostAsync(url, reqContent))
+            using (var respContent = resp.Content)
+            {
+                return await respContent.ReadAsStringAsync();
+            }
+        }
+
+        public static async Task<string> PostAsync<T>(this HttpClient client, IWeChatPayCertRequest<T> request, IDictionary<string, string> textParams) where T : WeChatPayResponse
         {
-            using (var reqContent = new StringContent(WeChatPayUtility.BuildContent(textParams), Encoding.UTF8, "application/xml"))
+            var url = request.GetRequestUrl();
+            var content = WeChatPayUtility.BuildContent(textParams);
+            using (var reqContent = new StringContent(content, Encoding.UTF8, "application/xml"))
             using (var resp = await client.PostAsync(url, reqContent))
             using (var respContent = resp.Content)
             {
                 return await respContent.ReadAsStringAsync();
             }
         }
+
+        public static async Task<(string serial, string timestamp, string nonce, string signature, string body, int statusCode)> GetAsync<T>(this HttpClient client, IWeChatPayV3GetRequest<T> request, WeChatPayOptions options) where T : WeChatPayV3Response
+        {
+            var url = request.GetRequestUrl();
+            var token = BuildToken(url, "GET", null, options);
+
+            client.DefaultRequestHeaders.Add(WeChatPayConsts.Wechatpay_Serial, options.CertificateSerialNo);
+            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("WECHATPAY2-SHA256-RSA2048", token);
+            client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(new ProductHeaderValue("Unknown")));
+            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
+
+            using (var resp = await client.GetAsync(url))
+            using (var respContent = resp.Content)
+            {
+                var serial = resp.Headers.GetValues(WeChatPayConsts.Wechatpay_Serial).First();
+                var timestamp = resp.Headers.GetValues(WeChatPayConsts.Wechatpay_Timestamp).First();
+                var nonce = resp.Headers.GetValues(WeChatPayConsts.Wechatpay_Nonce).First();
+                var signature = resp.Headers.GetValues(WeChatPayConsts.Wechatpay_Signature).First();
+                var body = await respContent.ReadAsStringAsync();
+                var statusCode = (int)resp.StatusCode;
+
+                return (serial, timestamp, nonce, signature, body, statusCode);
+            }
+        }
+
+        public static async Task<(string serial, string timestamp, string nonce, string signature, string body, int statusCode)> PostAsync<T>(this HttpClient client, IWeChatPayV3PostRequest<T> request, WeChatPayOptions options) where T : WeChatPayV3Response
+        {
+            var url = request.GetRequestUrl();
+            var content = SerializeQueryModel(request);
+            var token = BuildToken(url, "POST", content, options);
+            client.DefaultRequestHeaders.Add(WeChatPayConsts.Wechatpay_Serial, options.CertificateSerialNo);
+            client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("WECHATPAY2-SHA256-RSA2048", token);
+            client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(new ProductHeaderValue("Unknown")));
+            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
+
+            using (var reqContent = new StringContent(content, Encoding.UTF8, "application/json"))
+            using (var resp = await client.PostAsync(url, reqContent))
+            using (var respContent = resp.Content)
+            {
+                var serial = resp.Headers.GetValues(WeChatPayConsts.Wechatpay_Serial).First();
+                var timestamp = resp.Headers.GetValues(WeChatPayConsts.Wechatpay_Timestamp).First();
+                var nonce = resp.Headers.GetValues(WeChatPayConsts.Wechatpay_Nonce).First();
+                var signature = resp.Headers.GetValues(WeChatPayConsts.Wechatpay_Signature).First();
+                var body = await respContent.ReadAsStringAsync();
+                var statusCode = (int)resp.StatusCode;
+
+                return (serial, timestamp, nonce, signature, body, statusCode);
+            }
+        }
+
+        private static string SerializeQueryModel<T>(IWeChatPayV3PostRequest<T> request) where T : WeChatPayV3Response
+        {
+            var queryModel = request.GetQueryModel();
+            if (queryModel != null)
+            {
+                return JsonSerializer.Serialize(queryModel, queryModel.GetType(), jsonSerializerOptions);
+            }
+
+            throw new WeChatPayException("QueryModel is null!");
+        }
+
+        private static string BuildToken(string url, string method, string body, WeChatPayOptions options)
+        {
+            var uri = new Uri(url).PathAndQuery;
+            var timestamp = WeChatPayUtility.GetTimeStamp();
+            var nonce = WeChatPayUtility.GenerateNonceStr();
+            var message = BuildMessage(method, uri, timestamp, nonce, body);
+            var signature = options.CertificateRSAPrivateKey.Sign(message);
+
+            return $"mchid=\"{options.MchId}\",nonce_str=\"{nonce}\",timestamp=\"{timestamp}\",serial_no=\"{options.CertificateSerialNo}\",signature=\"{signature}\"";
+        }
+
+        private static string BuildMessage(string method, string uri, string timestamp, string nonce, string body)
+        {
+            return $"{method}\n{uri}\n{timestamp}\n{nonce}\n{body}\n";
+        }
     }
 }

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

@@ -11,7 +11,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay.Utility
             var sb = new StringBuilder();
             foreach (var iter in dictionary)
             {
-                if (!string.IsNullOrEmpty(iter.Value) && iter.Key != "sign")
+                if (!string.IsNullOrEmpty(iter.Value) && iter.Key != WeChatPayConsts.sign)
                 {
                     sb.Append(iter.Key).Append('=').Append(iter.Value).Append("&");
                 }

+ 0 - 16
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayCertificateManager.cs

@@ -1,16 +0,0 @@
-using System.Collections.Concurrent;
-using System.Security.Cryptography.X509Certificates;
-
-namespace Essensoft.AspNetCore.Payment.WeChatPay
-{
-    public class WeChatPayCertificateManager
-    {
-        private readonly ConcurrentDictionary<string, X509Certificate2> _certificateDictionary = new ConcurrentDictionary<string, X509Certificate2>();
-
-        public bool ContainsKey(string hash) => _certificateDictionary.ContainsKey(hash);
-
-        public bool TryAdd(string hash, X509Certificate2 certificate) => _certificateDictionary.TryAdd(hash, certificate);
-
-        public bool TryGetValue(string hash, out X509Certificate2 certificate) => _certificateDictionary.TryGetValue(hash, out certificate);
-    }
-}

+ 17 - 18
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayClient.cs

@@ -1,9 +1,12 @@
 using System;
-using System.IO;
 using System.Net.Http;
 using System.Security.Cryptography.X509Certificates;
+using System.Text;
 using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.Security;
 using Essensoft.AspNetCore.Payment.WeChatPay.Parser;
+using Essensoft.AspNetCore.Payment.WeChatPay.Request;
+using Essensoft.AspNetCore.Payment.WeChatPay.Response;
 using Essensoft.AspNetCore.Payment.WeChatPay.Utility;
 
 namespace Essensoft.AspNetCore.Payment.WeChatPay
@@ -13,14 +16,14 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
         public const string Prefix = nameof(WeChatPayClient) + ".";
 
         private readonly IHttpClientFactory _httpClientFactory;
-        private readonly WeChatPayCertificateManager _certificateManager;
+        private readonly WeChatPayClientCertificateManager _clientCertificateManager;
 
         #region WeChatPayClient Constructors
 
-        public WeChatPayClient(IHttpClientFactory httpClientFactory, WeChatPayCertificateManager certificateManager)
+        public WeChatPayClient(IHttpClientFactory httpClientFactory, WeChatPayClientCertificateManager clientCertificateManager)
         {
             _httpClientFactory = httpClientFactory;
-            _certificateManager = certificateManager;
+            _clientCertificateManager = clientCertificateManager;
         }
 
         #endregion
@@ -55,8 +58,8 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
             request.PrimaryHandler(options, signType, sortedTxtParams);
 
             var client = _httpClientFactory.CreateClient(nameof(WeChatPayClient));
-            var body = await client.PostAsync(request.GetRequestUrl(), sortedTxtParams);
-            var parser = new WeChatPayXmlParser<T>();
+            var body = await client.PostAsync(request, sortedTxtParams);
+            var parser = new WeChatPayResponseXmlParser<T>();
             var response = parser.Parse(body);
 
             if (request.GetNeedCheckSign())
@@ -149,18 +152,14 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 
             request.PrimaryHandler(options, signType, sortedTxtParams);
 
-            if (!_certificateManager.ContainsKey(options.CertificateHash))
+            if (!_clientCertificateManager.ContainsKey(options.CertificateSerialNo))
             {
-                var certificate = File.Exists(options.Certificate) ?
-                    new X509Certificate2(options.Certificate, options.CertificatePassword, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet) :
-                    new X509Certificate2(Convert.FromBase64String(options.Certificate), options.CertificatePassword, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet);
-
-                _certificateManager.TryAdd(options.CertificateHash, certificate);
+                _clientCertificateManager.TryAdd(options.CertificateSerialNo, options.X509Certificate2);
             }
 
-            var client = _httpClientFactory.CreateClient(Prefix + options.CertificateHash);
-            var body = await client.PostAsync(request.GetRequestUrl(), sortedTxtParams);
-            var parser = new WeChatPayXmlParser<T>();
+            var client = _httpClientFactory.CreateClient(Prefix + options.CertificateSerialNo);
+            var body = await client.PostAsync(request, sortedTxtParams);
+            var parser = new WeChatPayResponseXmlParser<T>();
             var response = parser.Parse(body);
 
             if (request.GetNeedCheckSign())
@@ -206,7 +205,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 
         #endregion
 
-        #region Common Method
+        #region Check Response Method
 
         private void CheckResponseSign(WeChatPayResponse response, WeChatPayOptions options, WeChatPaySignType signType)
         {
@@ -220,9 +219,9 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
                 throw new WeChatPayException("sign check fail: Parameters is Empty!");
             }
 
-            if (response.Parameters["return_code"] == "SUCCESS")
+            if (response.Parameters["return_code"] == WeChatPayCode.Success)
             {
-                if (!response.Parameters.TryGetValue("sign", out var sign))
+                if (!response.Parameters.TryGetValue(WeChatPayConsts.sign, out var sign))
                 {
                     throw new WeChatPayException("sign check fail: sign is Empty!");
                 }

+ 16 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayClientCertificateManager.cs

@@ -0,0 +1,16 @@
+using System.Collections.Concurrent;
+using System.Security.Cryptography.X509Certificates;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay
+{
+    public class WeChatPayClientCertificateManager
+    {
+        private readonly ConcurrentDictionary<string, X509Certificate2> _certificateDictionary = new ConcurrentDictionary<string, X509Certificate2>();
+
+        public bool ContainsKey(string serialNo) => _certificateDictionary.ContainsKey(serialNo);
+
+        public bool TryAdd(string serialNo, X509Certificate2 certificate) => _certificateDictionary.TryAdd(serialNo, certificate);
+
+        public bool TryGetValue(string serialNo, out X509Certificate2 certificate) => _certificateDictionary.TryGetValue(serialNo, out certificate);
+    }
+}

+ 8 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayConsts.cs

@@ -15,6 +15,8 @@
         public const string enc_bank_no = "enc_bank_no";
         public const string enc_true_name = "enc_true_name";
         public const string partnerid = "partnerid";
+        public const string prepayid = "prepayid";
+        public const string package = "package";
 
         public const string mch_appid = "mch_appid";
         public const string mchid = "mchid";
@@ -30,5 +32,11 @@
 
         public const string MD5 = "MD5";
         public const string HMAC_SHA256 = "HMAC-SHA256";
+        public const string RSA = "RSA";
+
+        public const string Wechatpay_Serial = "Wechatpay-Serial";
+        public const string Wechatpay_Timestamp = "Wechatpay-Timestamp";
+        public const string Wechatpay_Nonce = "Wechatpay-Nonce";
+        public const string Wechatpay_Signature = "Wechatpay-Signature";
     }
 }

+ 5 - 5
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayHandlerBuilderFilter.cs

@@ -7,11 +7,11 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 {
     public class WeChatPayHandlerBuilderFilter : IHttpMessageHandlerBuilderFilter
     {
-        private readonly WeChatPayCertificateManager _certificateManager;
+        private readonly WeChatPayClientCertificateManager _clientCertificateManager;
 
-        public WeChatPayHandlerBuilderFilter(WeChatPayCertificateManager certificateManager)
+        public WeChatPayHandlerBuilderFilter(WeChatPayClientCertificateManager clientCertificateManager)
         {
-            _certificateManager = certificateManager;
+            _clientCertificateManager = clientCertificateManager;
         }
 
         public Action<HttpMessageHandlerBuilder> Configure(Action<HttpMessageHandlerBuilder> next)
@@ -30,9 +30,9 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
                     if (builder.Name.Contains(WeChatPayClient.Prefix))
                     {
                         var hash = builder.Name.RemovePreFix(WeChatPayClient.Prefix);
-                        if (_certificateManager.TryGetValue(hash, out var certificate))
+                        if (_clientCertificateManager.TryGetValue(hash, out var clientCertificate))
                         {
-                            handler.ClientCertificates.Add(certificate);
+                            handler.ClientCertificates.Add(clientCertificate);
                         }
                     }
                 }

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

@@ -1,9 +1,27 @@
 #if NETCOREAPP3_1
 
+using System.Xml.Serialization;
+
 namespace Essensoft.AspNetCore.Payment.WeChatPay
 {
     public abstract class WeChatPayNotify : WeChatPayObject
     {
+        /// <summary>
+        /// 原始内容
+        /// </summary>
+        [XmlIgnore]
+        public string Body { get; set; }
+
+        /// <summary>
+        /// 原始参数
+        /// </summary>
+        [XmlIgnore]
+        public WeChatPayDictionary Parameters { get; internal set; }
+
+        /// <summary>
+        /// 处理 _$n / _$n_$m
+        /// </summary>
+        internal virtual void Execute() { }
     }
 }
 

+ 1 - 1
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayNotifyClient.cs

@@ -44,7 +44,7 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
             }
 
             var body = await new StreamReader(request.Body, Encoding.UTF8).ReadToEndAsync();
-            var parser = new WeChatPayXmlParser<T>();
+            var parser = new WeChatPayNotifyXmlParser<T>();
             var notify = parser.Parse(body);
             if (notify is WeChatPayRefundNotify)
             {

+ 1 - 19
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayObject.cs

@@ -1,24 +1,6 @@
-using System.Xml.Serialization;
-
-namespace Essensoft.AspNetCore.Payment.WeChatPay
+namespace Essensoft.AspNetCore.Payment.WeChatPay
 {
     public abstract class WeChatPayObject
     {
-        /// <summary>
-        /// 原始内容
-        /// </summary>
-        [XmlIgnore]
-        public string Body { get; set; }
-
-        /// <summary>
-        /// 原始参数
-        /// </summary>
-        [XmlIgnore]
-        public WeChatPayDictionary Parameters { get; internal set; }
-
-        /// <summary>
-        /// 处理 _$n / _$n_$m
-        /// </summary>
-        internal virtual void Execute() { }
     }
 }

+ 28 - 11
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayOptions.cs

@@ -1,4 +1,7 @@
-using Essensoft.AspNetCore.Payment.Security;
+using System;
+using System.IO;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
 
 namespace Essensoft.AspNetCore.Payment.WeChatPay
 {
@@ -7,7 +10,9 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
     /// </summary>
     public class WeChatPayOptions
     {
-        internal string CertificateHash;
+        internal X509Certificate2 X509Certificate2;
+        internal RSA CertificateRSAPrivateKey;
+        internal string CertificateSerialNo;
 
         private string certificate;
         private string certificatePassword;
@@ -31,23 +36,19 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
 
         /// <summary>
         /// 子商户应用号
-        /// 仅服务商使用
+        /// 仅服务商使用
         /// </summary>
         public string SubAppId { get; set; }
 
         /// <summary>
         /// 子商户号
-        /// 仅服务商使用
+        /// 仅服务商使用
         /// </summary>
         public string SubMchId { get; set; }
 
         /// <summary>
-        /// API密钥
-        /// </summary>
-        public string Key { get; set; }
-
-        /// <summary>
-        /// API证书(文件名/文件的Base64编码)
+        /// API证书
+        /// 证书文件路径/证书文件的base64字符串
         /// </summary>
         public string Certificate
         {
@@ -57,7 +58,13 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
                 if (!string.IsNullOrEmpty(value))
                 {
                     certificate = value;
-                    CertificateHash = MD5.Compute(certificate);
+
+                    X509Certificate2 = File.Exists(certificate) ?
+                        new X509Certificate2(certificate, CertificatePassword, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet) :
+                        new X509Certificate2(Convert.FromBase64String(certificate), CertificatePassword, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet);
+
+                    CertificateRSAPrivateKey = X509Certificate2.GetRSAPrivateKey();
+                    CertificateSerialNo = X509Certificate2.GetSerialNumberString();
                 }
             }
         }
@@ -72,6 +79,16 @@ namespace Essensoft.AspNetCore.Payment.WeChatPay
             set => certificatePassword = value;
         }
 
+        /// <summary>
+        /// API密钥
+        /// </summary>
+        public string Key { get; set; }
+
+        /// <summary>
+        /// APIv3密钥
+        /// </summary>
+        public string V3Key { get; set; }
+
         /// <summary>
         /// RSA公钥
         /// 目前仅调用"企业付款到银行卡API"时使用,执行"获取RSA加密公钥API"即可获取。

+ 16 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayPlatformCertificateManager.cs

@@ -0,0 +1,16 @@
+using System.Collections.Concurrent;
+using System.Security.Cryptography.X509Certificates;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay
+{
+    public class WeChatPayPlatformCertificateManager
+    {
+        private readonly ConcurrentDictionary<string, X509Certificate2> _certificateDictionary = new ConcurrentDictionary<string, X509Certificate2>();
+
+        public bool ContainsKey(string serialNo) => _certificateDictionary.ContainsKey(serialNo);
+
+        public bool TryAdd(string serialNo, X509Certificate2 certificate) => _certificateDictionary.TryAdd(serialNo, certificate);
+
+        public bool TryGetValue(string serialNo, out X509Certificate2 certificate) => _certificateDictionary.TryGetValue(serialNo, out certificate);
+    }
+}

+ 19 - 1
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayResponse.cs

@@ -1,6 +1,24 @@
-namespace Essensoft.AspNetCore.Payment.WeChatPay
+using System.Xml.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay
 {
     public abstract class WeChatPayResponse : WeChatPayObject
     {
+        /// <summary>
+        /// 原始内容
+        /// </summary>
+        [XmlIgnore]
+        public string Body { get; set; }
+
+        /// <summary>
+        /// 原始参数
+        /// </summary>
+        [XmlIgnore]
+        public WeChatPayDictionary Parameters { get; internal set; }
+
+        /// <summary>
+        /// 处理 _$n / _$n_$m
+        /// </summary>
+        internal virtual void Execute() { }
     }
 }

+ 43 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayTradeState.cs

@@ -0,0 +1,43 @@
+namespace Essensoft.AspNetCore.Payment.WeChatPay
+{
+    /// <summary>
+    /// 交易状态
+    /// </summary>
+    public static class WeChatPayTradeState
+    {
+        /// <summary>
+        /// 支付成功
+        /// </summary>
+        public const string Success = "SUCCESS";
+
+        /// <summary>
+        /// 转入退款
+        /// </summary>
+        public const string Refund = "REFUND";
+
+        /// <summary>
+        /// 未支付
+        /// </summary>
+        public const string NotPay = "NOTPAY";
+
+        /// <summary>
+        /// 已关闭
+        /// </summary>
+        public const string Closed = "CLOSED";
+
+        /// <summary>
+        /// 已撤销(付款码支付)
+        /// </summary>
+        public const string Revoked = "REVOKED";
+
+        /// <summary>
+        /// 用户支付中(付款码支付)
+        /// </summary>
+        public const string UserPaying = "USERPAYING";
+
+        /// <summary>
+        /// 支付失败(其他原因,如银行返回失败)
+        /// </summary>
+        public const string PayError = "PAYERROR";
+    }
+}

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

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

+ 30 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayV3Notify.cs

@@ -0,0 +1,30 @@
+#if NETCOREAPP3_1
+
+using System.Text.Json.Serialization;
+using Essensoft.AspNetCore.Payment.WeChatPay.Domain;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay
+{
+    public abstract class WeChatPayV3Notify : WeChatPayObject
+    {
+        /// <summary>
+        /// 通知原始内容
+        /// </summary>
+        [JsonIgnore]
+        public string Body { get; set; }
+
+        /// <summary>
+        /// 通知密文
+        /// </summary>
+        [JsonIgnore]
+        public NotifyCiphertext NotifyCiphertext { get; set; }
+
+        /// <summary>
+        /// 通知明文内容
+        /// </summary>
+        [JsonIgnore]
+        public string ResourcePlaintext { get; set; }
+    }
+}
+
+#endif

+ 125 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayV3NotifyClient.cs

@@ -0,0 +1,125 @@
+#if NETCOREAPP3_1
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading.Tasks;
+using Essensoft.AspNetCore.Payment.Security;
+using Essensoft.AspNetCore.Payment.WeChatPay.Parser;
+using Essensoft.AspNetCore.Payment.WeChatPay.Request;
+using Microsoft.AspNetCore.Http;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay
+{
+    public class WeChatPayV3NotifyClient : IWeChatPayV3NotifyClient
+    {
+        #region WeChatPayV3NotifyClient Constructors
+
+        private readonly WeChatPayPlatformCertificateManager _platformCertificateManager;
+        private readonly IWeChatPayV3Client _client;
+
+        public WeChatPayV3NotifyClient(WeChatPayPlatformCertificateManager platformCertificateManager, IWeChatPayV3Client client)
+        {
+            _platformCertificateManager = platformCertificateManager;
+            _client = client;
+        }
+
+        #endregion
+
+        #region IWeChatPayV3NotifyClient Members
+
+        public async Task<T> ExecuteAsync<T>(HttpRequest request, WeChatPayOptions options) where T : WeChatPayV3Notify
+        {
+            if (options == null)
+            {
+                throw new ArgumentNullException(nameof(options));
+            }
+
+            if (string.IsNullOrEmpty(options.V3Key))
+            {
+                throw new ArgumentNullException(nameof(options.V3Key));
+            }
+
+            var body = await new StreamReader(request.Body, Encoding.UTF8).ReadToEndAsync();
+
+            await CheckNotifySignAsync(request, body, options);
+
+            var parser = new WeChatPayV3NotifyJsonParser<T>();
+            var notify = parser.Parse(body, options.V3Key);
+
+            return notify;
+        }
+
+        #endregion
+
+        #region Check Notify Method
+
+        private async Task CheckNotifySignAsync(HttpRequest request, string body, WeChatPayOptions options)
+        {
+            if (string.IsNullOrEmpty(body))
+            {
+                throw new WeChatPayException("sign check fail: body is empty!");
+            }
+
+            request.Headers.TryGetValue(WeChatPayConsts.Wechatpay_Serial, out var serialValues);
+            request.Headers.TryGetValue(WeChatPayConsts.Wechatpay_Timestamp, out var timestampValues);
+            request.Headers.TryGetValue(WeChatPayConsts.Wechatpay_Nonce, out var nonceValues);
+            request.Headers.TryGetValue(WeChatPayConsts.Wechatpay_Signature, out var signatureValues);
+
+            var serial = serialValues.First();
+            var timestamp = timestampValues.First();
+            var nonce = nonceValues.First();
+            var signature = signatureValues.First();
+
+            if (string.IsNullOrEmpty(serial))
+            {
+                throw new WeChatPayException($"sign check fail: {nameof(serial)} is empty!");
+            }
+
+            if (string.IsNullOrEmpty(signature))
+            {
+                throw new WeChatPayException($"sign check fail: {nameof(signature)} is empty!");
+            }
+
+            var cert = await LoadPlatformCertificateAsync(serial, options);
+            var signatureSourceDate = BuildSignatureSourceDate(timestamp, nonce, body);
+
+            if (!cert.GetRSAPublicKey().Verify(signatureSourceDate, signature))
+            {
+                throw new WeChatPayException("sign check fail: check Sign and Data Fail!");
+            }
+        }
+
+        private string BuildSignatureSourceDate(string timestamp, string nonce, string body)
+        {
+            return $"{timestamp}\n{nonce}\n{body}\n";
+        }
+
+        private async Task<X509Certificate2> LoadPlatformCertificateAsync(string serial, WeChatPayOptions options)
+        {
+            // 如果证书序列号已缓存,则直接使用缓存的
+            if (_platformCertificateManager.TryGetValue(serial, out var certificate2))
+            {
+                return certificate2;
+            }
+
+            // 否则重新下载新的平台证书
+            var request = new WeChatPayCertificatesRequest();
+            var response = await _client.ExecuteAsync(request, options);
+            if (response.Certificates.Count > 0 && _platformCertificateManager.TryGetValue(serial, out certificate2))
+            {
+                return certificate2;
+            }
+            else
+            {
+                throw new WeChatPayException("Download certificates failed!");
+            }
+        }
+
+        #endregion
+    }
+}
+
+#endif

+ 27 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayV3NotifyResult.cs

@@ -0,0 +1,27 @@
+#if NETCOREAPP3_1
+
+using Microsoft.AspNetCore.Mvc;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay
+{
+    /// <summary>
+    /// WeChatPay V3 通知应答。
+    /// </summary>
+    public static class WeChatPayV3NotifyResult
+    {
+        private static readonly ContentResult _success = new ContentResult { Content = "{\"code\":\"SUCCESS\",\"message\":\"SUCCESS\"}", ContentType = "application/json", StatusCode = 200 };
+        private static readonly ContentResult _failure = new ContentResult { Content = "{\"code\":\"FAIL\",\"message\":\"FAIL\"}", ContentType = "application/json", StatusCode = 200 };
+
+        /// <summary>
+        /// 成功
+        /// </summary>
+        public static IActionResult Success => _success;
+
+        /// <summary>
+        /// 失败
+        /// </summary>
+        public static IActionResult Failure => _failure;
+    }
+}
+
+#endif

+ 38 - 0
src/Essensoft.AspNetCore.Payment.WeChatPay/WeChatPayV3Response.cs

@@ -0,0 +1,38 @@
+using System.Text.Json.Serialization;
+using Essensoft.AspNetCore.Payment.WeChatPay.Domain;
+
+namespace Essensoft.AspNetCore.Payment.WeChatPay
+{
+    public abstract class WeChatPayV3Response : WeChatPayObject
+    {
+        /// <summary>
+        /// 原始内容
+        /// </summary>
+        [JsonIgnore]
+        public string Body { get; set; }
+
+        /// <summary>
+        /// HTTP状态码
+        /// </summary>
+        [JsonIgnore]
+        public int StatusCode { get; set; }
+
+        /// <summary>
+        /// 错误码
+        /// </summary>
+        [JsonPropertyName("code")]
+        public string Code { get; set; }
+
+        /// <summary>
+        /// 错误详情
+        /// </summary>
+        [JsonPropertyName("detail")]
+        public ErrorDetail Detail { get; set; }
+
+        /// <summary>
+        /// 错误信息
+        /// </summary>
+        [JsonPropertyName("message")]
+        public string Message { get; set; }
+    }
+}