瀏覽代碼

签入代码

李广 2 月之前
父節點
當前提交
9fcf15ee4d
共有 100 個文件被更改,包括 7733 次插入29 次删除
  1. 283 29
      .gitignore
  2. 105 0
      PaySharp.Alipay/AlipayGateway.cs
  3. 81 0
      PaySharp.Alipay/CertEnvironment.cs
  4. 16 0
      PaySharp.Alipay/Domain/AppPayModel.cs
  5. 135 0
      PaySharp.Alipay/Domain/BarcodePayModel.cs
  6. 119 0
      PaySharp.Alipay/Domain/BasePayModel.cs
  7. 26 0
      PaySharp.Alipay/Domain/BillDownloadModel.cs
  8. 6 0
      PaySharp.Alipay/Domain/CancelModel.cs
  9. 13 0
      PaySharp.Alipay/Domain/CloseModel.cs
  10. 54 0
      PaySharp.Alipay/Domain/ExtUserInfo.cs
  11. 44 0
      PaySharp.Alipay/Domain/ExtendParam.cs
  12. 63 0
      PaySharp.Alipay/Domain/Goods.cs
  13. 34 0
      PaySharp.Alipay/Domain/QueryModel.cs
  14. 57 0
      PaySharp.Alipay/Domain/RefundModel.cs
  15. 16 0
      PaySharp.Alipay/Domain/RefundQueryModel.cs
  16. 106 0
      PaySharp.Alipay/Domain/ScanPayModel.cs
  17. 29 0
      PaySharp.Alipay/Domain/TradeFundBill.cs
  18. 65 0
      PaySharp.Alipay/Domain/TransferModel.cs
  19. 36 0
      PaySharp.Alipay/Domain/TransferQueryModel.cs
  20. 74 0
      PaySharp.Alipay/Domain/VoucherDetail.cs
  21. 32 0
      PaySharp.Alipay/Domain/WapPayModel.cs
  22. 49 0
      PaySharp.Alipay/Domain/WebPayModel.cs
  23. 116 0
      PaySharp.Alipay/Merchant.cs
  24. 21 0
      PaySharp.Alipay/PaySharp.Alipay.csproj
  25. 二進制
      PaySharp.Alipay/PaySharp.snk
  26. 57 0
      PaySharp.Alipay/PaySharpConfigExtensions.cs
  27. 13 0
      PaySharp.Alipay/Request/AppPayRequest.cs
  28. 49 0
      PaySharp.Alipay/Request/BarcodePayRequest.cs
  29. 21 0
      PaySharp.Alipay/Request/BaseRequest.cs
  30. 13 0
      PaySharp.Alipay/Request/BillDownloadRequest.cs
  31. 13 0
      PaySharp.Alipay/Request/CancelRequest.cs
  32. 13 0
      PaySharp.Alipay/Request/CloseRequest.cs
  33. 13 0
      PaySharp.Alipay/Request/QueryRequest.cs
  34. 13 0
      PaySharp.Alipay/Request/RefundQueryRequest.cs
  35. 13 0
      PaySharp.Alipay/Request/RefundRequest.cs
  36. 13 0
      PaySharp.Alipay/Request/ScanPayRequest.cs
  37. 13 0
      PaySharp.Alipay/Request/TransferQueryRequest.cs
  38. 13 0
      PaySharp.Alipay/Request/TransferRequest.cs
  39. 13 0
      PaySharp.Alipay/Request/WapPayRequest.cs
  40. 13 0
      PaySharp.Alipay/Request/WebPayRequest.cs
  41. 20 0
      PaySharp.Alipay/Response/AppPayResponse.cs
  42. 241 0
      PaySharp.Alipay/Response/BarcodePayResponse.cs
  43. 49 0
      PaySharp.Alipay/Response/BaseResponse.cs
  44. 46 0
      PaySharp.Alipay/Response/BillDownloadResponse.cs
  45. 33 0
      PaySharp.Alipay/Response/CancelResponse.cs
  46. 21 0
      PaySharp.Alipay/Response/CloseResponse.cs
  47. 178 0
      PaySharp.Alipay/Response/NotifyResponse.cs
  48. 146 0
      PaySharp.Alipay/Response/QueryResponse.cs
  49. 43 0
      PaySharp.Alipay/Response/RefundQueryResponse.cs
  50. 85 0
      PaySharp.Alipay/Response/RefundResponse.cs
  51. 21 0
      PaySharp.Alipay/Response/ScanPayResponse.cs
  52. 65 0
      PaySharp.Alipay/Response/TransferQueryResponse.cs
  53. 32 0
      PaySharp.Alipay/Response/TransferResponse.cs
  54. 20 0
      PaySharp.Alipay/Response/WapPayResponse.cs
  55. 20 0
      PaySharp.Alipay/Response/WebPayResponse.cs
  56. 46 0
      PaySharp.Alipay/ServiceCollectionExtensions.cs
  57. 91 0
      PaySharp.Alipay/SubmitProcess.cs
  58. 716 0
      PaySharp.Alipay/Util/AlipaySignature.cs
  59. 326 0
      PaySharp.Alipay/Util/AntCertificationUtil.cs
  60. 34 0
      PaySharp.Alipay/Util/ArgumentValidator.cs
  61. 33 0
      PaySharp.Alipay/Util/Asymmetric/AsymmetricManager.cs
  62. 114 0
      PaySharp.Alipay/Util/Asymmetric/BaseAsymmetricEncryptor.cs
  63. 47 0
      PaySharp.Alipay/Util/Asymmetric/IAsymmetricEncryptor.cs
  64. 18 0
      PaySharp.Alipay/Util/Asymmetric/RSA2Encryptor.cs
  65. 282 0
      PaySharp.Alipay/Util/Asymmetric/RSAEncryptor.cs
  66. 110 0
      PaySharp.Alipay/Util/Asymmetric/SM2Encryptor.cs
  67. 23 0
      PaySharp.Alipay/Util/SignSourceData.cs
  68. 11 0
      PaySharp.Core/Attributes/IgnoreAttribute.cs
  69. 12 0
      PaySharp.Core/Attributes/ReNameAttribute.cs
  70. 35 0
      PaySharp.Core/ConfigurationHandler.cs
  71. 18 0
      PaySharp.Core/Events/CancelSucceedEventArgs.cs
  72. 61 0
      PaySharp.Core/Events/NotifyEventArgs.cs
  73. 21 0
      PaySharp.Core/Events/PaySucceedEventArgs.cs
  74. 18 0
      PaySharp.Core/Events/RefundSucceedEventArgs.cs
  75. 24 0
      PaySharp.Core/Events/UnKnownNotifyEventArgs.cs
  76. 21 0
      PaySharp.Core/Events/UnknownGatewayEventArgs.cs
  77. 12 0
      PaySharp.Core/Exceptions/GatewayException.cs
  78. 112 0
      PaySharp.Core/Gateways/BaseGateway.cs
  79. 595 0
      PaySharp.Core/Gateways/GatewayData.cs
  80. 114 0
      PaySharp.Core/Gateways/Gateways.cs
  81. 10 0
      PaySharp.Core/Gateways/IGateway.cs
  82. 35 0
      PaySharp.Core/Gateways/IGateways.cs
  83. 32 0
      PaySharp.Core/Gateways/NullGateway.cs
  84. 23 0
      PaySharp.Core/IMerchant.cs
  85. 142 0
      PaySharp.Core/Notify/Notify.cs
  86. 112 0
      PaySharp.Core/Notify/NotifyProcess.cs
  87. 18 0
      PaySharp.Core/Notify/NotifyType.cs
  88. 21 0
      PaySharp.Core/PaySharp.Core.csproj
  89. 二進制
      PaySharp.Core/PaySharp.snk
  90. 56 0
      PaySharp.Core/Request/Request.cs
  91. 7 0
      PaySharp.Core/Response/IResponse.cs
  92. 45 0
      PaySharp.Core/ServiceCollectionExtensions.cs
  93. 741 0
      PaySharp.Core/Utils/EncryptUtil.cs
  94. 382 0
      PaySharp.Core/Utils/HttpUtil.cs
  95. 145 0
      PaySharp.Core/Utils/StringUtil.cs
  96. 47 0
      PaySharp.Core/Utils/Util.cs
  97. 29 0
      PaySharp.Core/Utils/ValidateUtil.cs
  98. 74 0
      PaySharp.Wechatpay/ConvertUtil.cs
  99. 31 0
      PaySharp.Wechatpay/Domain/AppPayModel.cs
  100. 6 0
      PaySharp.Wechatpay/Domain/AppletPayModel.cs

+ 283 - 29
.gitignore

@@ -1,30 +1,84 @@
-# ---> C Sharp
-# Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
-[Bb]in/
-[Oo]bj/
-
-# mstest test results
-TestResults
-
 ## Ignore Visual Studio temporary files, build results, and
 ## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
 
 # User-specific files
+*.rsuser
 *.suo
 *.user
+*.userosscache
 *.sln.docstates
 
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
 # Build results
 [Dd]ebug/
+[Dd]ebugPublic/
 [Rr]elease/
+[Rr]eleases/
 x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
 *_i.c
 *_p.c
+*_h.h
 *.ilk
 *.meta
 *.obj
+*.iobj
 *.pch
 *.pdb
+*.ipdb
 *.pgc
 *.pgd
 *.rsp
@@ -33,35 +87,83 @@ x64/
 *.tli
 *.tlh
 *.tmp
+*.tmp_proj
+*_wpftmp.csproj
 *.log
 *.vspscc
 *.vssscc
 .builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
 
 # Visual C++ cache files
 ipch/
 *.aps
 *.ncb
+*.opendb
 *.opensdf
 *.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
 
 # Visual Studio profiler
 *.psess
 *.vsp
 *.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
 
 # Guidance Automation Toolkit
 *.gpState
 
 # ReSharper is a .NET coding add-in
-_ReSharper*
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
 
 # NCrunch
-*.ncrunch*
+_NCrunch_*
 .*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
 
 # Installshield output folder
-[Ee]xpress
+[Ee]xpress/
 
 # DocProject is a documentation generator add-in
 DocProject/buildhelp/
@@ -74,37 +176,189 @@ DocProject/Help/Html2
 DocProject/Help/html
 
 # Click-Once directory
-publish
+publish/
 
 # Publish Web Output
-*.Publish.xml
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
 
-# NuGet Packages Directory
-packages
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
 
-# Windows Azure Build Output
-csx
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
 *.build.csdef
 
-# Windows Store app package directory
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
 AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
 
 # Others
-[Bb]in
-[Oo]bj
-sql
-TestResults
-[Tt]est[Rr]esult*
-*.Cache
-ClientBin
-[Ss]tyle[Cc]op.*
+ClientBin/
 ~$*
+*~
 *.dbmdl
-Generated_Code #added for RIA/Silverlight projects
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
 
-# Backup & report files from converting an old project file to a newer
-# Visual Studio version. Backup files are not needed, because we have git ;-)
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
 _UpgradeReport_Files/
 Backup*/
 UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
 
+*.DS_Store

+ 105 - 0
PaySharp.Alipay/AlipayGateway.cs

@@ -0,0 +1,105 @@
+#if NETCOREAPP3_1
+using Microsoft.Extensions.Options;
+#endif
+using System.Threading.Tasks;
+using PaySharp.Alipay.Request;
+using PaySharp.Alipay.Response;
+using PaySharp.Core;
+using PaySharp.Core.Exceptions;
+using PaySharp.Core.Request;
+using PaySharp.Core.Utils;
+
+namespace PaySharp.Alipay
+{
+    /// <summary>
+    /// 支付宝网关
+    /// </summary>
+    public sealed class AlipayGateway : BaseGateway
+    {
+        #region 私有字段
+
+        private readonly Merchant _merchant;
+
+        #endregion
+
+        #region 构造函数
+
+        /// <summary>
+        /// 初始化支付宝网关
+        /// </summary>
+        /// <param name="merchant">商户数据</param>
+        public AlipayGateway(Merchant merchant)
+            : base(merchant)
+        {
+            _merchant = merchant;
+        }
+
+#if NETCOREAPP3_1
+
+        /// <summary>
+        /// 初始化支付宝网关
+        /// </summary>
+        /// <param name="merchant">商户数据</param>
+        public AlipayGateway(IOptions<Merchant> merchant)
+            : this(merchant.Value)
+        {
+        }
+
+#endif
+
+        #endregion
+
+        #region 属性
+
+        public override string GatewayUrl { get; set; } = "https://openapi.alipay.com";
+
+        public new NotifyResponse NotifyResponse => (NotifyResponse)base.NotifyResponse;
+
+        protected override bool IsPaySuccess => NotifyResponse.TradeStatus == "TRADE_SUCCESS" && !IsRefundSuccess;
+
+        protected override bool IsRefundSuccess => NotifyResponse.RefundAmount > 0;
+
+        protected override bool IsCancelSuccess { get; }
+
+        protected override string[] NotifyVerifyParameter => new string[]
+        {
+            "app_id","version", "charset","trade_no", "sign","sign_type"
+        };
+
+        #endregion
+
+        #region 公共方法
+
+        protected override async Task<bool> ValidateNotifyAsync()
+        {
+            base.NotifyResponse = await GatewayData.ToObjectAsync<NotifyResponse>(StringCase.Snake);
+            base.NotifyResponse.Raw = GatewayData.ToUrl(false);
+            GatewayData.Remove("sign");
+            GatewayData.Remove("sign_type");
+
+            var result = EncryptUtil.Verify(
+                GatewayData.ToUrl(false),
+                NotifyResponse.Sign,
+                _merchant.CertEnvironment == null ? _merchant.AlipayPublicKey : _merchant.CertEnvironment.GetAlipayPublicKey(GatewayData.GetStringValue("alipay_cert_sn"))
+            );
+            if (result)
+            {
+                return true;
+            }
+
+            throw new GatewayException("签名不一致");
+        }
+
+        public override TResponse Execute<TModel, TResponse>(Request<TModel, TResponse> request)
+        {
+            if (request is WapPayRequest || request is WebPayRequest || request is AppPayRequest)
+            {
+                return SubmitProcess.SdkExecute(_merchant, request, GatewayUrl);
+            }
+
+            return SubmitProcess.Execute(_merchant, request, GatewayUrl);
+        }
+
+        #endregion
+    }
+}

+ 81 - 0
PaySharp.Alipay/CertEnvironment.cs

@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Org.BouncyCastle.X509;
+using PaySharp.Alipay.Util;
+using System.Linq;
+
+namespace PaySharp.Alipay
+{
+    /// <summary>
+    /// 证书模式运行时环境
+    /// </summary>
+    public class CertEnvironment
+    {
+
+        /// <summary>
+        /// 支付宝根证书内容
+        /// </summary>
+        public string RootCertContent { get; set; }
+
+        /// <summary>
+        /// 支付宝根证书序列号
+        /// </summary>
+        public string RootCertSN { get; set; }
+
+        /// <summary>
+        /// 商户应用公钥证书序列号
+        /// </summary>
+        public string MerchantCertSN { get; set; }
+
+        /// <summary>
+        /// 缓存的不同支付宝公钥证书序列号对应的支付宝公钥
+        /// </summary>
+        private readonly Dictionary<string, string> CachedAlipayPublicKey = new Dictionary<string, string>();
+
+        /// <summary>
+        /// 构造证书运行环境
+        /// </summary>
+        /// <param name="merchantCertPath">商户公钥证书路径</param>
+        /// <param name="alipayCertPath">支付宝公钥证书路径</param>
+        /// <param name="alipayRootCertPath">支付宝根证书路径</param>
+        public CertEnvironment(string merchantCertPath, string alipayCertPath, string alipayRootCertPath)
+        {
+            if (string.IsNullOrEmpty(merchantCertPath) || string.IsNullOrEmpty(alipayCertPath) || string.IsNullOrEmpty(alipayCertPath))
+            {
+                throw new Exception("证书参数merchantCertPath、alipayCertPath或alipayRootCertPath设置不完整。");
+            }
+
+            this.RootCertContent = File.ReadAllText(alipayRootCertPath);
+            this.RootCertSN = AntCertificationUtil.GetRootCertSN(RootCertContent);
+
+            X509Certificate merchantCert = AntCertificationUtil.ParseCert(File.ReadAllText(merchantCertPath));
+            this.MerchantCertSN = AntCertificationUtil.GetCertSN(merchantCert);
+
+            X509Certificate alipayCert = AntCertificationUtil.ParseCert(File.ReadAllText(alipayCertPath));
+            string alipayCertSN = AntCertificationUtil.GetCertSN(alipayCert);
+            string alipayPublicKey = AntCertificationUtil.ExtractPemPublicKeyFromCert(alipayCert);
+            CachedAlipayPublicKey[alipayCertSN] = alipayPublicKey;
+        }
+
+        public string GetAlipayPublicKey(string sn)
+        {
+            //如果没有指定sn,则默认取缓存中的第一个值
+            if (string.IsNullOrEmpty(sn))
+            {
+                return CachedAlipayPublicKey.Values.FirstOrDefault();
+            }
+
+            if (CachedAlipayPublicKey.ContainsKey(sn))
+            {
+                return CachedAlipayPublicKey[sn];
+            }
+            else
+            {
+                //网关在支付宝公钥证书变更前,一定会确认通知到商户并在商户做出反馈后,才会更新该商户的支付宝公钥证书
+                //TODO: 后续可以考虑加入自动升级支付宝公钥证书逻辑,注意并发更新冲突问题
+                throw new Exception("支付宝公钥证书[" + sn + "]已过期,请重新下载最新支付宝公钥证书并替换原证书文件");
+            }
+        }
+    }
+}

+ 16 - 0
PaySharp.Alipay/Domain/AppPayModel.cs

@@ -0,0 +1,16 @@
+namespace PaySharp.Alipay.Domain
+{
+    /// <summary>
+    /// 手机支付模型
+    /// </summary>
+    public class AppPayModel : BasePayModel
+    {
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        public AppPayModel()
+            : base("QUICK_MSECURITY_PAY")
+        {
+        }
+    }
+}

+ 135 - 0
PaySharp.Alipay/Domain/BarcodePayModel.cs

@@ -0,0 +1,135 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace PaySharp.Alipay.Domain
+{
+    /// <summary>
+    /// 条码支付模型
+    /// </summary>
+    [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
+    public class BarcodePayModel
+    {
+        /// <summary>
+        /// 商户订单号,64个字符以内、可包含字母、数字、下划线;需保证在商户端不重复
+        /// </summary>
+        [StringLength(64, ErrorMessage = "商户订单号最大长度为64位")]
+        [Required(ErrorMessage = "请设置商户订单号")]
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 支付场景 
+        /// </summary>
+        public string Scene => "bar_code";
+
+        /// <summary>
+        /// 支付授权码,25~30开头的长度为16~24位的数字,实际字符串长度以开发者获取的付款码长度为准
+        /// </summary>
+        [StringLength(32, ErrorMessage = "支付授权码最大长度为32位")]
+        public string AuthCode { get; set; }
+
+        /// <summary>
+        /// 销售产品码,与支付宝签约的产品码名称。 
+        /// </summary>
+        public string ProductCode => "FACE_TO_FACE_PAYMENT";
+
+        /// <summary>
+        /// 订单标题
+        /// </summary>
+        [StringLength(256, ErrorMessage = "订单标题最大长度为256位")]
+        [Required(ErrorMessage = "请设置订单标题")]
+        public string Subject { get; set; }
+
+        /// <summary>
+        /// 买家的支付宝用户id,如果为空,会从传入了码值信息中获取买家ID
+        /// </summary>
+        [StringLength(28, ErrorMessage = "买家的支付宝用户id最大长度为28位")]
+        public string BuyerId { get; set; }
+
+        /// <summary>
+        /// 卖家支付宝用户号,如果该值为空,则默认为商户签约账号对应的支付宝用户ID
+        /// </summary>
+        [StringLength(28, ErrorMessage = "卖家支付宝用户号最大长度为28位")]
+        public string SellerId { get; set; }
+
+        /// <summary>
+        /// 订单总金额,单位为元
+        /// </summary>
+        [Required(ErrorMessage = "请设置订单总金额")]
+        public double TotalAmount { get; set; }
+
+        /// <summary>
+        /// 标价币种, total_amount 对应的币种单位。支持英镑:GBP、港币:HKD、美元:USD、新加坡元:SGD、日元:JPY、加拿大元:CAD、澳元:AUD、欧元:EUR、新西兰元:NZD、韩元:KRW、泰铢:THB、瑞士法郎:CHF、瑞典克朗:SEK、丹麦克朗:DKK、挪威克朗:NOK、马来西亚林吉特:MYR、印尼卢比:IDR、菲律宾比索:PHP、毛里求斯卢比:MUR、以色列新谢克尔:ILS、斯里兰卡卢比:LKR、俄罗斯卢布:RUB、阿联酋迪拉姆:AED、捷克克朗:CZK、南非兰特:ZAR、人民币:CNY
+        /// </summary>
+        [StringLength(8, ErrorMessage = "标价币种最大长度为8位")]
+        public string TransCurrency { get; set; }
+
+        /// <summary>
+        /// 商户指定的结算币种,支持英镑:GBP、港币:HKD、美元:USD、新加坡元:SGD、日元:JPY、加拿大元:CAD、澳元:AUD、欧元:EUR、新西兰元:NZD、韩元:KRW、泰铢:THB、瑞士法郎:CHF、瑞典克朗:SEK、丹麦克朗:DKK、挪威克朗:NOK、马来西亚林吉特:MYR、印尼卢比:IDR、菲律宾比索:PHP、毛里求斯卢比:MUR、以色列新谢克尔:ILS、斯里兰卡卢比:LKR、俄罗斯卢布:RUB、阿联酋迪拉姆:AED、捷克克朗:CZK、南非兰特:ZAR、人民币:CNY
+        /// </summary>
+        [StringLength(8, ErrorMessage = "结算币种最大长度为8位")]
+        public string SettleCurrency { get; set; }
+
+        /// <summary>
+        /// 参与优惠计算的金额,单位为元,精确到小数点后两位,取值范围[0.01,100000000]。 
+        /// 如果该值未传入,但传入了【订单总金额】和【不可打折金额】,则该值默认为【订单总金额】-【不可打折金额】
+        /// </summary>
+        public double DiscountableAmount { get; set; }
+
+        /// <summary>
+        /// 订单描述
+        /// </summary>
+        [StringLength(128, ErrorMessage = "商户订单号最大长度为128位")]
+        public string Body { get; set; }
+
+        /// <summary>
+        /// 订单包含的商品列表信息
+        /// </summary>
+        public List<Goods> GoodsDetail { get; set; }
+
+        /// <summary>
+        /// 卖家端自定义的的操作员编号
+        /// </summary>
+        [StringLength(28, ErrorMessage = "卖家端自定义的的操作员编号最大长度为28位")]
+        public string OperatorId { get; set; }
+
+        /// <summary>
+        /// 商户门店编号。该参数用于请求参数中以区分各门店,非必传项。
+        /// </summary>
+        [StringLength(32, ErrorMessage = "商户门店编号最大长度为32位")]
+        public string StoreId { get; set; }
+
+        /// <summary>
+        /// 商户的终端编号
+        /// </summary>
+        [StringLength(32, ErrorMessage = "终端编号最大长度为32位")]
+        public string TerminalId { get; set; }
+
+        /// <summary>
+        /// 业务扩展参数,详见业务扩展参数说明 https://docs.open.alipay.com/#kzcs
+        /// </summary>
+        public ExtendParam ExtendParams { get; set; }
+
+        /// <summary>
+        /// 该笔订单允许的最晚付款时间,逾期将关闭交易。
+        /// 取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)。
+        /// 该参数数值不接受小数点, 如 1.5h,可转换为 90m。该参数在请求到支付宝时开始计时。
+        /// </summary>
+        [StringLength(6, ErrorMessage = "该笔订单允许的最晚付款时间最大长度为6位")]
+        public string TimeoutExpress { get; set; }
+
+        /// <summary>
+        /// 预授权确认模式,授权转交易请求中传入,适用于预授权转交易业务使用,目前只支持PRE_AUTH(预授权产品码) 
+        /// COMPLETE:转交易支付完成结束预授权,解冻剩余金额; NOT_COMPLETE:转交易支付完成不结束预授权,不解冻剩余金额
+        /// </summary>
+        [StringLength(32, ErrorMessage = "预授权确认模式最大长度为32位")]
+        public string AuthConfirmMode { get; set; }
+
+        /// <summary>
+        /// 商户传入终端设备相关信息,具体值要和支付宝约定
+        /// </summary>
+        [StringLength(2048, ErrorMessage = "商户传入终端设备相关信息最大长度为2048位")]
+        public string TerminalParams { get; set; }
+    }
+}

+ 119 - 0
PaySharp.Alipay/Domain/BasePayModel.cs

@@ -0,0 +1,119 @@
+using System.ComponentModel.DataAnnotations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace PaySharp.Alipay.Domain
+{
+    /// <summary>
+    /// 支付基础模型
+    /// 目前仅适用于App,Web,Wap
+    /// </summary>
+    [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
+    public class BasePayModel
+    {
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        /// <param name="productCode">销售产品码</param>
+        public BasePayModel(string productCode)
+        {
+            ProductCode = productCode;
+        }
+
+        /// <summary>
+        /// 商户订单号,64个字符以内、可包含字母、数字、下划线;需保证在商户端不重复
+        /// </summary>
+        [StringLength(64, ErrorMessage = "商户订单号最大长度为64位")]
+        [Required(ErrorMessage = "请设置商户订单号")]
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 销售产品码,与支付宝签约的产品码名称。 
+        /// </summary>
+        public string ProductCode { get; private set; }
+
+        /// <summary>
+        /// 订单总金额,单位为元
+        /// </summary>
+        [Required(ErrorMessage = "请设置订单总金额")]
+        public double TotalAmount { get; set; }
+
+        /// <summary>
+        /// 订单标题
+        /// </summary>
+        [StringLength(256, ErrorMessage = "订单标题最大长度为256位")]
+        [Required(ErrorMessage = "请设置订单标题")]
+        public string Subject { get; set; }
+
+        /// <summary>
+        /// 订单描述
+        /// </summary>
+        [StringLength(128, ErrorMessage = "商户订单号最大长度为128位")]
+        public string Body { get; set; }
+
+        /// <summary>
+        /// 业务扩展参数,详见业务扩展参数说明 https://docs.open.alipay.com/#kzcs
+        /// </summary>
+        public ExtendParam ExtendParams { get; set; }
+
+        /// <summary>
+        /// 该笔订单允许的最晚付款时间,逾期将关闭交易。
+        /// 取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)。
+        /// 该参数数值不接受小数点, 如 1.5h,可转换为 90m。该参数在请求到支付宝时开始计时。
+        /// </summary>
+        [StringLength(6, ErrorMessage = "该笔订单允许的最晚付款时间最大长度为6位")]
+        public string TimeoutExpress { get; set; }
+
+        /// <summary>
+        /// 公用回传参数,如果请求时传递了该参数,则返回给商户时会回传该参数。
+        /// 支付宝只会在异步通知时将该参数原样返回。本参数必须进行UrlEncode之后才可以发送给支付宝
+        /// </summary>
+        [StringLength(512, ErrorMessage = "公用回传参数最大长度为512位")]
+        public string PassbackParams { get; set; }
+
+        /// <summary>
+        /// 商品主类型:0—虚拟类商品,1—实物类商品(默认
+        /// 注:虚拟类商品不支持使用花呗渠道
+        /// </summary>
+        [Range(0, 1, ErrorMessage = "商品主类型只能为0或1")]
+        public int GoodsType { get; set; }
+
+        /// <summary>
+        /// 绝对超时时间,格式为yyyy-MM-dd HH:mm。 
+        /// 注:1)以支付宝系统时间为准;2)如果和timeout_express参数同时传入,以time_expire为准。
+        /// </summary>
+        [StringLength(32, ErrorMessage = "绝对超时时间最大长度为32位")]
+        public string TimeExpire { get; set; }
+
+        /// <summary>
+        /// 可用渠道,用户只能在指定渠道范围内支付,当有多个渠道时用“,”分隔
+        /// 注:与disable_pay_channels互斥 https://docs.open.alipay.com/#qdsm
+        /// </summary>
+        [StringLength(128, ErrorMessage = "可用渠道最大长度为128位")]
+        public string EnablePayChannels { get; set; }
+
+        /// <summary>
+        /// 禁用渠道,用户不可用指定渠道支付,当有多个渠道时用“,”分隔
+		/// 注:与enable_pay_channels互斥 https://docs.open.alipay.com/#qdsm
+        /// </summary>
+        [StringLength(128, ErrorMessage = "禁用渠道最大长度为128位")]
+        public string DisablePayChannels { get; set; }
+
+        /// <summary>
+        /// 优惠参数 注:仅与支付宝协商后可用
+        /// </summary>
+        [StringLength(512, ErrorMessage = "优惠参数最大长度为512位")]
+        public string PromoParams { get; set; }
+
+        /// <summary>
+        /// 商户门店编号。该参数用于请求参数中以区分各门店,非必传项。
+        /// </summary>
+        [StringLength(32, ErrorMessage = "商户门店编号最大长度为32位")]
+        public string StoreId { get; set; }
+
+        /// <summary>
+        /// 外部指定买家,详见外部用户ExtUserInfo参数说明 https://docs.open.alipay.com/#wbsh
+        /// </summary>
+        public ExtUserInfo ExtUserInfo { get; set; }
+    }
+}

+ 26 - 0
PaySharp.Alipay/Domain/BillDownloadModel.cs

@@ -0,0 +1,26 @@
+using System.ComponentModel.DataAnnotations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace PaySharp.Alipay.Domain
+{
+    [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
+    public class BillDownloadModel
+    {
+        /// <summary>
+        /// 账单类型,商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下
+        /// 账单类型:trade、signcustomer;trade指商户基于支付宝交易收单的业务账单;
+        /// signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单;
+        /// </summary>
+        [Required(ErrorMessage = "请设置账单类型")]
+        [StringLength(10, ErrorMessage = "账单类型最大长度为10位")]
+        public string BillType { get; set; }
+
+        /// <summary>
+        /// 账单时间:日账单格式为yyyy-MM-dd,月账单格式为yyyy-MM。
+        /// </summary>
+        [Required(ErrorMessage = "请设置账单时间")]
+        [StringLength(15, ErrorMessage = "账单时间最大长度为15位")]
+        public string BillDate { get; set; }
+    }
+}

+ 6 - 0
PaySharp.Alipay/Domain/CancelModel.cs

@@ -0,0 +1,6 @@
+namespace PaySharp.Alipay.Domain
+{
+    public class CancelModel : QueryModel
+    {
+    }
+}

+ 13 - 0
PaySharp.Alipay/Domain/CloseModel.cs

@@ -0,0 +1,13 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace PaySharp.Alipay.Domain
+{
+    public class CloseModel : QueryModel
+    {
+        /// <summary>
+        /// 卖家端自定义的的操作员编号
+        /// </summary>
+        [StringLength(28, ErrorMessage = "卖家端自定义的的操作员编号最大长度为28位")]
+        public string OperatorId { get; set; }
+    }
+}

+ 54 - 0
PaySharp.Alipay/Domain/ExtUserInfo.cs

@@ -0,0 +1,54 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace PaySharp.Alipay.Domain
+{
+    /// <summary>
+    /// 外部指定买家
+    /// </summary>
+    [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
+    public class ExtUserInfo
+    {
+        /// <summary>
+        /// 证件号
+        /// 注:need_check_info=T时该参数才有效
+        /// </summary>
+        public string CertNo { get; set; }
+
+        /// <summary>
+        /// 身份证:IDENTITY_CARD、护照:PASSPORT、军官证:OFFICER_CARD、士兵证:SOLDIER_CARD、户口本:HOKOU等。如有其它类型需要支持,请与蚂蚁金服工作人员联系。
+        /// 注: need_check_info=T时该参数才有效
+        /// </summary>
+        public string CertType { get; set; }
+
+        /// <summary>
+        /// 是否强制校验付款人身份信息
+        /// T:强制校验,F:不强制
+        /// </summary>
+        public string FixBuyer { get; set; }
+
+        /// <summary>
+        /// 允许的最小买家年龄,买家年龄必须大于等于所传数值
+        /// 注:1. need_check_info=T时该参数才有效  2. min_age为整数,必须大于等于0
+        /// </summary>
+        public string MinAge { get; set; }
+
+        /// <summary>
+        /// 手机号
+        /// 注:该参数暂不校验
+        /// </summary>
+        public string Mobile { get; set; }
+
+        /// <summary>
+        /// 姓名
+        /// 注:need_check_info=T时该参数才有效
+        /// </summary>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// 是否强制校验身份信息
+        /// T:强制校验,F:不强制
+        /// </summary>
+        public string NeedCheckInfo { get; set; }
+    }
+}

+ 44 - 0
PaySharp.Alipay/Domain/ExtendParam.cs

@@ -0,0 +1,44 @@
+using System.ComponentModel.DataAnnotations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace PaySharp.Alipay.Domain
+{
+    [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
+    public class ExtendParam
+    {
+        /// <summary>
+        /// 系统商编号,该参数作为系统商返佣数据提取的依据,请填写系统商签约协议的PID
+        /// </summary>
+        [StringLength(64, ErrorMessage = "系统商编号最大长度为64位")]
+        public string SysServiceProviderId { get; set; }
+
+        /// <summary>
+        /// 花呗分期数(目前仅支持3、6、12
+		/// 注:使用该参数需要仔细阅读“花呗分期接入文档” https://docs.open.alipay.com/277/106748
+        /// </summary>
+        [StringLength(5, ErrorMessage = "花呗分期数最大长度为5位")]
+        public string HbFqNum { get; set; }
+
+        /// <summary>
+        /// 卖家承担收费比例,商家承担手续费传入100,用户承担手续费传入0,仅支持传入100、0两种,其他比例暂不支持
+		/// 注:使用该参数需要仔细阅读“花呗分期接入文档” https://docs.open.alipay.com/277/106748
+        /// </summary>
+        [StringLength(3, ErrorMessage = "卖家承担收费比例最大长度为3位")]
+        public string HbFqSellerPercent { get; set; }
+
+        /// <summary>
+        /// 是否发起实名校验 T:发起 F:不发起
+        /// </summary>
+        [StringLength(1, ErrorMessage = "是否发起实名校验最大长度为1位")]
+        [JsonProperty("needBuyerRealnamed")]
+        public string NeedBuyerRealnamed { get; set; }
+
+        /// <summary>
+        /// 账务备注 注:该字段显示在离线账单的账务备注中
+        /// </summary>
+        [StringLength(128, ErrorMessage = "账务备注最大长度为128位")]
+        [JsonProperty("TRANS_MEMO")]
+        public string TransMemo { get; set; }
+    }
+}

+ 63 - 0
PaySharp.Alipay/Domain/Goods.cs

@@ -0,0 +1,63 @@
+using System.ComponentModel.DataAnnotations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace PaySharp.Alipay.Domain
+{
+    [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
+    public class Goods
+    {
+        /// <summary>
+        /// 商品的编号
+        /// </summary>
+        [JsonProperty("goods_id")]
+        [StringLength(32, ErrorMessage = "商品的编号最大长度为32位")]
+        [Required(ErrorMessage = "请设置商品的编号")]
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 支付宝定义的统一商品编号
+        /// </summary>
+        [StringLength(32, ErrorMessage = "商品的编号最大长度为32位")]
+        public string AlipayGoodsId { get; set; }
+
+        /// <summary>
+        /// 商品名称
+        /// </summary>
+        [JsonProperty("goods_name")]
+        [StringLength(256, ErrorMessage = "商品名称最大长度为256位")]
+        [Required(ErrorMessage = "请设置商品名称")]
+        public string Name { get; set; }
+
+        /// <summary>
+        /// 商品数量
+        /// </summary>
+        [Required(ErrorMessage = "请设置商品数量")]
+        public int Quantity { get; set; }
+
+        /// <summary>
+        /// 商品单价,单位为元
+        /// </summary>
+        [Required(ErrorMessage = "请设置商品单价")]
+        public double Price { get; set; }
+
+        /// <summary>
+        /// 商品类目
+        /// </summary>
+        [JsonProperty("goods_category")]
+        [StringLength(24, ErrorMessage = "商品类目最大长度为24位")]
+        public string Category { get; set; }
+
+        /// <summary>
+        /// 商品描述信息
+        /// </summary>
+        [StringLength(1000, ErrorMessage = "商品描述信息最大长度为1000位")]
+        public string Body { get; set; }
+
+        /// <summary>
+        /// 商品的展示地址
+        /// </summary>
+        [StringLength(400, ErrorMessage = "商品的展示地址最大长度为400位")]
+        public string ShowUrl { get; set; }
+    }
+}

+ 34 - 0
PaySharp.Alipay/Domain/QueryModel.cs

@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace PaySharp.Alipay.Domain
+{
+    [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
+    public class QueryModel : IValidatableObject
+    {
+        /// <summary>
+        /// 订单支付时传入的商户订单号,和支付宝交易号不能同时为空。 
+        /// TradeNo,OutTradeNo如果同时存在优先取TradeNo
+        /// </summary>
+        [StringLength(64, ErrorMessage = "商户订单号最大长度为64位")]
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 支付宝交易号,和商户订单号不能同时为空
+        /// </summary>
+        [StringLength(64, ErrorMessage = "支付宝交易号最大长度为64位")]
+        public string TradeNo { get; set; }
+
+        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
+        {
+            if (string.IsNullOrEmpty(OutTradeNo) && string.IsNullOrEmpty(TradeNo))
+            {
+                yield return new ValidationResult("商户订单号和支付宝交易号不能同时为空");
+            }
+
+            yield return ValidationResult.Success;
+        }
+    }
+}

+ 57 - 0
PaySharp.Alipay/Domain/RefundModel.cs

@@ -0,0 +1,57 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using Newtonsoft.Json;
+
+namespace PaySharp.Alipay.Domain
+{
+    public class RefundModel : QueryModel
+    {
+        /// <summary>
+        /// 需要退款的金额,该金额不能大于订单金额,单位为元
+        /// </summary>
+        public double RefundAmount { get; set; }
+
+        /// <summary>
+        /// 订单退款币种信息,非外币交易,不能传入退款币种信息
+        /// </summary>
+        [StringLength(8, ErrorMessage = "币种信息最大长度为8位")]
+        public string RefundCurrency { get; set; }
+
+        /// <summary>
+        /// 退款的原因说明
+        /// </summary>
+        [StringLength(256, ErrorMessage = "退款的原因说明最大长度为256位")]
+        public string RefundReason { get; set; }
+
+        /// <summary>
+        /// 标识一次退款请求,同一笔交易多次退款需要保证唯一,如需部分退款,则此参数必传。
+        /// </summary>
+        [StringLength(64, ErrorMessage = "退款请求号最大长度为64位")]
+        [Required(ErrorMessage = "请设置退款请求号")]
+        [JsonProperty("out_request_no")]
+        public string OutRefundNo { get; set; }
+
+        /// <summary>
+        /// 卖家端自定义的的操作员编号
+        /// </summary>
+        [StringLength(28, ErrorMessage = "卖家端自定义的的操作员编号最大长度为28位")]
+        public string OperatorId { get; set; }
+
+        /// <summary>
+        /// 商户的门店编号
+        /// </summary>
+        [StringLength(32, ErrorMessage = "门店编号最大长度为32位")]
+        public string StoreId { get; set; }
+
+        /// <summary>
+        /// 商户的终端编号
+        /// </summary>
+        [StringLength(32, ErrorMessage = "终端编号最大长度为32位")]
+        public string TerminalId { get; set; }
+
+        /// <summary>
+        /// 退款包含的商品列表信息
+        /// </summary>
+        public List<Goods> GoodsDetail { get; set; }
+    }
+}

+ 16 - 0
PaySharp.Alipay/Domain/RefundQueryModel.cs

@@ -0,0 +1,16 @@
+using System.ComponentModel.DataAnnotations;
+using Newtonsoft.Json;
+
+namespace PaySharp.Alipay.Domain
+{
+    public class RefundQueryModel : QueryModel
+    {
+        /// <summary>
+        /// 请求退款接口时,传入的退款请求号,如果在退款请求时未传入,则该值为创建交易时的外部交易号
+        /// </summary>
+        [Required(ErrorMessage = "请设置退款请求号")]
+        [StringLength(64, ErrorMessage = "退款请求号最大长度为64位")]
+        [JsonProperty("out_request_no")]
+        public string OutRefundNo { get; set; }
+    }
+}

+ 106 - 0
PaySharp.Alipay/Domain/ScanPayModel.cs

@@ -0,0 +1,106 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace PaySharp.Alipay.Domain
+{
+    [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
+    public class ScanPayModel
+    {
+        /// <summary>
+        /// 商户订单号,64个字符以内、可包含字母、数字、下划线;需保证在商户端不重复
+        /// </summary>
+        [StringLength(64, ErrorMessage = "商户订单号最大长度为64位")]
+        [Required(ErrorMessage = "请设置商户订单号")]
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 卖家支付宝用户ID。 如果该值为空,则默认为商户签约账号对应的支付宝用户ID
+        /// </summary>
+        [StringLength(28, ErrorMessage = "卖家支付宝用户ID最大长度为28位")]
+        public string SellerId { get; set; }
+
+        /// <summary>
+        /// 订单总金额,单位为元
+        /// </summary>
+        [Required(ErrorMessage = "请设置订单总金额")]
+        public double TotalAmount { get; set; }
+
+        /// <summary>
+        /// 可打折金额. 参与优惠计算的金额,单位为元,精确到小数点后两位,取值范围[0.01,100000000] 如果该值未传入,但传入了【订单总金额】,【不可打折金额】则该值默认为【订单总金额】-【不可打折金额】
+        /// </summary>
+        public double DiscountableAmount { get; set; }
+
+        /// <summary>
+        /// 订单标题
+        /// </summary>
+        [StringLength(256, ErrorMessage = "订单标题最大长度为256位")]
+        [Required(ErrorMessage = "请设置订单标题")]
+        public string Subject { get; set; }
+
+        /// <summary>
+        /// 订单包含的商品列表信息
+        /// </summary>
+        public List<Goods> GoodsDetail { get; set; }
+
+        /// <summary>
+        /// 订单描述
+        /// </summary>
+        [StringLength(128, ErrorMessage = "商户订单号最大长度为128位")]
+        public string Body { get; set; }
+
+        /// <summary>
+        /// 卖家端自定义的的操作员编号
+        /// </summary>
+        [StringLength(28, ErrorMessage = "卖家端自定义的的操作员编号最大长度为28位")]
+        public string OperatorId { get; set; }
+
+        /// <summary>
+        /// 商户门店编号。该参数用于请求参数中以区分各门店,非必传项。
+        /// </summary>
+        [StringLength(32, ErrorMessage = "商户门店编号最大长度为32位")]
+        public string StoreId { get; set; }
+
+        /// <summary>
+        /// 禁用渠道,用户不可用指定渠道支付,当有多个渠道时用“,”分隔
+        /// 注:与enable_pay_channels互斥 https://docs.open.alipay.com/#qdsm
+        /// </summary>
+        [StringLength(128, ErrorMessage = "禁用渠道最大长度为128位")]
+        public string DisablePayChannels { get; set; }
+
+        /// <summary>
+        /// 可用渠道,用户只能在指定渠道范围内支付,当有多个渠道时用“,”分隔
+        /// 注:与disable_pay_channels互斥 https://docs.open.alipay.com/#qdsm
+        /// </summary>
+        [StringLength(128, ErrorMessage = "可用渠道最大长度为128位")]
+        public string EnablePayChannels { get; set; }
+
+        /// <summary>
+        /// 商户的终端编号
+        /// </summary>
+        [StringLength(32, ErrorMessage = "终端编号最大长度为32位")]
+        public string TerminalId { get; set; }
+
+        /// <summary>
+        /// 业务扩展参数,详见业务扩展参数说明 https://docs.open.alipay.com/#kzcs
+        /// </summary>
+        public ExtendParam ExtendParams { get; set; }
+
+        /// <summary>
+        /// 该笔订单允许的最晚付款时间,逾期将关闭交易。
+        /// 取值范围:1m~15d。m-分钟,h-小时,d-天,1c-当天(1c-当天的情况下,无论交易何时创建,都在0点关闭)。
+        /// 该参数数值不接受小数点, 如 1.5h,可转换为 90m。该参数在请求到支付宝时开始计时。
+        /// </summary>
+        [StringLength(6, ErrorMessage = "该笔订单允许的最晚付款时间最大长度为6位")]
+        public string TimeoutExpress { get; set; }
+
+        /// <summary>
+        /// 商户传入业务信息,具体值要和支付宝约定 
+        /// 将商户传入信息分发给相应系统,应用于安全,营销等参数直传场景
+        /// 格式为json格式
+        /// </summary>
+        [StringLength(512, ErrorMessage = "商户传入业务信息最大长度为512位")]
+        public string BusinessParams { get; set; }
+    }
+}

+ 29 - 0
PaySharp.Alipay/Domain/TradeFundBill.cs

@@ -0,0 +1,29 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace PaySharp.Alipay.Domain
+{
+    [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
+    public class TradeFundBill
+    {
+        /// <summary>
+        /// 交易使用的资金渠道,详见 https://doc.open.alipay.com/doc2/detail?treeId=26&amp;articleId=103259&amp;docType=1
+        /// </summary>
+        public string FundChannel { get; set; }
+
+        /// <summary>
+        /// 银行卡支付时的银行代码
+        /// </summary>
+        public string BankCode { get; set; }
+
+        /// <summary>
+        /// 该支付工具类型所使用的金额
+        /// </summary>
+        public double Amount { get; set; }
+
+        /// <summary>
+        /// 渠道实际付款金额
+        /// </summary>
+        public double RealAmount { get; set; }
+    }
+}

+ 65 - 0
PaySharp.Alipay/Domain/TransferModel.cs

@@ -0,0 +1,65 @@
+using System.ComponentModel.DataAnnotations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace PaySharp.Alipay.Domain
+{
+    [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
+    public class TransferModel
+    {
+        /// <summary>
+        /// 商户转账唯一订单号。发起转账来源方定义的转账单据ID,用于将转账回执通知给来源方。 
+        /// 不同来源方给出的ID可以重复,同一个来源方必须保证其ID的唯一性。 
+        /// 只支持半角英文、数字,及“-”、“_”。
+        /// </summary>
+        [StringLength(64, ErrorMessage = "商户转账唯一订单号最大长度为64位")]
+        [Required(ErrorMessage = "请设置商户转账唯一订单号")]
+        [JsonProperty("out_biz_no")]
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 收款方账户类型。可取值: 
+        /// 1、ALIPAY_USERID:支付宝账号对应的支付宝唯一用户号。以2088开头的16位纯数字组成。 
+        /// 2、ALIPAY_LOGONID:支付宝登录号,支持邮箱和手机号格式。
+        /// </summary>
+        [StringLength(20, ErrorMessage = "收款方账户类型最大长度为20位")]
+        [Required(ErrorMessage = "请设置收款方账户类型")]
+        public string PayeeType { get; set; }
+
+        /// <summary>
+        /// 收款方账户。与payee_type配合使用。付款方和收款方不能是同一个账户。
+        /// </summary>
+        [StringLength(100, ErrorMessage = "收款方账户最大长度为100位")]
+        [Required(ErrorMessage = "请设置收款方账户")]
+        public string PayeeAccount { get; set; }
+
+        /// <summary>
+        /// 转账金额,单位:元。 
+        /// 只支持2位小数,小数点前最大支持13位,金额必须大于等于0.1元。 
+        /// 最大转账金额以实际签约的限额为准。
+        /// </summary>
+        public double Amount { get; set; }
+
+        /// <summary>
+        /// 付款方姓名(最长支持100个英文/50个汉字)。
+        /// 显示在收款方的账单详情页。如果该字段不传,则默认显示付款方的支付宝认证姓名或单位名称。
+        /// </summary>
+        [StringLength(100, ErrorMessage = "付款方姓名最大长度为100位")]
+        public string PayerShowName { get; set; }
+
+        /// <summary>
+        /// 收款方真实姓名(最长支持100个英文/50个汉字)。 
+        /// 如果本参数不为空,则会校验该账户在支付宝登记的实名是否与收款方真实姓名一致。
+        /// </summary>
+        [StringLength(100, ErrorMessage = "收款方真实姓名最大长度为100位")]
+        public string PayeeRealName { get; set; }
+
+        /// <summary>
+        /// 转账备注(支持200个英文/100个汉字)。
+        /// 当付款方为企业账户,且转账金额达到(大于等于)50000元,remark不能为空。
+        /// 收款方可见,会展示在收款用户的收支详情中。
+        /// </summary>
+        [StringLength(200, ErrorMessage = "收款方真实姓名最大长度为200位")]
+        public string Remark { get; set; }
+    }
+}

+ 36 - 0
PaySharp.Alipay/Domain/TransferQueryModel.cs

@@ -0,0 +1,36 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace PaySharp.Alipay.Domain
+{
+    [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
+    public class TransferQueryModel : IValidatableObject
+    {
+        /// <summary>
+        /// 商户转账唯一订单号:发起转账来源方定义的转账单据ID。 
+        /// 和支付宝转账单据号不能同时为空。当和支付宝转账单据号同时提供时,将用支付宝转账单据号进行查询,忽略本参数
+        /// </summary>
+        [StringLength(64, ErrorMessage = "商户转账唯一订单号最大长度为64位")]
+        [JsonProperty("out_biz_no")]
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 支付宝转账单据号:和商户转账唯一订单号不能同时为空。当和商户转账唯一订单号同时提供时,将用本参数进行查询,忽略商户转账唯一订单号。
+        /// </summary>
+        [StringLength(64, ErrorMessage = "支付宝转账单据号最大长度为64位")]
+        [JsonProperty("order_id")]
+        public string TradeNo { get; set; }
+
+        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
+        {
+            if (string.IsNullOrEmpty(OutTradeNo) && string.IsNullOrEmpty(TradeNo))
+            {
+                yield return new ValidationResult("商户转账唯一订单号和支付宝转账单据号不能同时为空");
+            }
+
+            yield return ValidationResult.Success;
+        }
+    }
+}

+ 74 - 0
PaySharp.Alipay/Domain/VoucherDetail.cs

@@ -0,0 +1,74 @@
+using System.ComponentModel.DataAnnotations;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace PaySharp.Alipay.Domain
+{
+    [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
+    public class VoucherDetail
+    {
+        /// <summary>
+        /// 券编号
+        /// </summary>
+        [StringLength(32, ErrorMessage = "券编号最大长度为32位")]
+        [Required(ErrorMessage = "请设置券编号")]
+        public string Id { get; set; }
+
+        /// <summary>
+        /// 券名称
+        /// </summary>
+        [StringLength(64, ErrorMessage = "券名称最大长度为64位")]
+        [Required(ErrorMessage = "请设置券名称")]
+        public string Name { get; set; }
+
+        /// <summary>
+        /// 券类型
+        /// 当前有三种类型:  ALIPAY_FIX_VOUCHER - 全场代金券  ALIPAY_DISCOUNT_VOUCHER - 折扣券  ALIPAY_ITEM_VOUCHER - 单品优惠  注:不排除将来新增其他类型的可能,商家接入时注意兼容性避免硬编码
+        /// </summary>
+        [StringLength(32, ErrorMessage = "优惠券类型最大长度为32位")]
+        [Required(ErrorMessage = "请设置优惠券类型")]
+        public string Type { get; set; }
+
+        /// <summary>
+        /// 优惠券面额,它应该会等于商家出资加上其他出资方出资
+        /// </summary>
+        public double Amount { get; set; }
+
+        /// <summary>
+        /// 商家出资(特指发起交易的商家出资金额)
+        /// </summary>
+        public double? MerchantContribute { get; set; }
+
+        /// <summary>
+        /// 其他出资方出资金额,可能是支付宝,可能是品牌商,或者其他方,也可能是他们的一起出资
+        /// </summary>
+        public double? OtherContribute { get; set; }
+
+        /// <summary>
+        /// 优惠券备注信息
+        /// </summary>
+        [StringLength(256, ErrorMessage = "优惠券备注信息最大长度为256位")]
+        public string Memo { get; set; }
+
+        /// <summary>
+        /// 券模板编号
+        /// </summary>
+        [StringLength(64, ErrorMessage = "券模板编号最大长度为64位")]
+        public string TemplateId { get; set; }
+
+        /// <summary>
+        /// 如果使用的这张券是用户购买的,则该字段代表用户在购买这张券时平台优惠的金额
+        /// </summary>
+        public double PurchaseAntContribute { get; set; }
+
+        /// <summary>
+        /// 如果使用的这张券是用户购买的,则该字段代表用户在购买这张券时用户实际付款的金额
+        /// </summary>
+        public double PurchaseBuyerContribute { get; set; }
+
+        /// <summary>
+        /// 如果使用的这张券是用户购买的,则该字段代表用户在购买这张券时商户优惠的金额
+        /// </summary>
+        public double PurchaseMerchantContribute { get; set; }
+    }
+}

+ 32 - 0
PaySharp.Alipay/Domain/WapPayModel.cs

@@ -0,0 +1,32 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace PaySharp.Alipay.Domain
+{
+    /// <summary>
+    /// 手机网站支付模型
+    /// </summary>
+    public class WapPayModel : BasePayModel
+    {
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        public WapPayModel()
+            : base("QUICK_WAP_WAY")
+        {
+        }
+
+        /// <summary>
+        /// 获取用户授权信息,可实现如免登功能。
+        /// 获取方法请查阅:用户信息授权 https://docs.open.alipay.com/289/105656
+        /// </summary>
+        [StringLength(40, ErrorMessage = "用户授权信息最大长度为40位")]
+        public string AuthToken { get; set; }
+
+        /// <summary>
+        /// 添加该参数后在h5支付收银台会出现返回按钮,可用于用户付款中途退出并返回到该参数指定的商户网站地址。
+        /// 注:该参数对支付宝钱包标准收银台下的跳转不生效。
+        /// </summary>
+        [StringLength(400, ErrorMessage = "退出地址最大长度为400位")]
+        public string QuitUrl { get; set; }
+    }
+}

+ 49 - 0
PaySharp.Alipay/Domain/WebPayModel.cs

@@ -0,0 +1,49 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace PaySharp.Alipay.Domain
+{
+    /// <summary>
+    /// 电脑网站支付模型
+    /// </summary>
+    public class WebPayModel : BasePayModel
+    {
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        public WebPayModel()
+            : base("FAST_INSTANT_TRADE_PAY")
+        {
+        }
+
+        /// <summary>
+        /// 订单包含的商品列表信息,Json格式: {"show_url":"https://或http://打头的商品的展示地址"} ,在支付时,可点击商品名称跳转到该地址
+        /// </summary>
+        public Goods[] GoodsDetail { get; set; }
+
+        /// <summary>
+        /// 获取用户授权信息,可实现如免登功能。
+        /// 获取方法请查阅:用户信息授权 https://docs.open.alipay.com/289/105656
+        /// </summary>
+        [StringLength(40, ErrorMessage = "用户授权信息最大长度为40位")]
+        public string AuthToken { get; set; }
+
+        /// <summary>
+        /// PC扫码支付的方式,支持前置模式和跳转模式。
+        /// 前置模式是将二维码前置到商户的订单确认页的模式。需要商户在自己的页面中以iframe方式请求支付宝页面。具体分为以下几种:
+        /// 0:订单码-简约前置模式,对应iframe宽度不能小于600px,高度不能小于300px;
+        /// 1:订单码-前置模式,对应iframe宽度不能小于300px,高度不能小于600px;
+        /// 3:订单码-迷你前置模式,对应iframe宽度不能小于75px,高度不能小于75px;
+        /// 4:订单码-可定义宽度的嵌入式二维码,商户可根据需要设定二维码的大小。
+        /// 跳转模式下,用户的扫码界面是由支付宝生成的,不在商户的域名下。
+        /// 2:订单码-跳转模式
+        /// </summary>
+        [StringLength(2, ErrorMessage = "PC扫码支付的方式最大长度为2位")]
+        public string QrPayMode { get; set; }
+
+        /// <summary>
+        /// 商户自定义二维码宽度 注:qr_pay_mode=4时该参数生效
+        /// </summary>
+        [StringLength(4, ErrorMessage = "商户自定义二维码宽度最大长度为4位")]
+        public string QrcodeWidth { get; set; }
+    }
+}

+ 116 - 0
PaySharp.Alipay/Merchant.cs

@@ -0,0 +1,116 @@
+using System;
+using System.ComponentModel.DataAnnotations;
+using PaySharp.Core;
+
+namespace PaySharp.Alipay
+{
+    public class Merchant : IMerchant
+    {
+        #region 属性
+
+        /// <summary>
+        /// 应用ID
+        /// </summary>
+        [Required(ErrorMessage = "请输入支付机构提供的应用编号")]
+        public string AppId { get; set; }
+
+        /// <summary>
+        /// 签名类型
+        /// </summary>
+        public string SignType { get; set; } = "RSA2";
+
+        /// <summary>
+        /// 格式
+        /// </summary>
+        public string Format => "JSON";
+
+        /// <summary>
+        /// 时间戳
+        /// </summary>
+        public string Timestamp => DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+
+        /// <summary>
+        /// 版本
+        /// </summary>
+        public string Version => "1.0";
+
+        /// <summary>
+        /// 编码格式
+        /// </summary>
+        public string Charset => "UTF-8";
+
+        /// <summary>
+        /// 商户私钥
+        /// </summary>
+        [Required(ErrorMessage = "请设置商户私钥")]
+        [Ignore]
+        public string Privatekey { get; set; }
+
+        /// <summary>
+        /// 支付宝公钥
+        /// 查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥
+        /// </summary>
+        [Required(ErrorMessage = "请设置支付宝公钥")]
+        [Ignore]
+        public string AlipayPublicKey { get; set; }
+
+        private string returnUrl;
+        /// <summary>
+        /// 返回地址
+        /// </summary>
+        public string ReturnUrl
+        {
+            get => returnUrl;
+            set
+            {
+                if (value.StartsWith("http") || value.StartsWith("https"))
+                {
+                    returnUrl = value;
+                }
+                else
+                {
+                    throw new FormatException("返回地址必须以http或https开头");
+                }
+            }
+        }
+
+        /// <summary>
+        /// 网关回发通知URL
+        /// </summary>
+        public string NotifyUrl { get; set; }
+
+        /// <summary>
+        /// 支付宝公钥证书文件路径
+        /// </summary>
+        public string AlipayCertPath { get; set; }
+        /// <summary>
+        /// 应用公钥证书文件路径
+        /// </summary>
+        public string MerchantCertPath { get; set; }
+        /// <summary>
+        /// 支付宝根证书文件路径
+        /// </summary>
+        public string AlipayRootCertPath { get; set; }
+
+        #endregion
+
+        private CertEnvironment certEnvironment;
+        /// <summary>
+        /// 证书模式运行时环境
+        /// </summary>
+        public CertEnvironment CertEnvironment
+        {
+            get
+            {
+                if (string.IsNullOrEmpty(MerchantCertPath) && string.IsNullOrEmpty(AlipayCertPath) && string.IsNullOrEmpty(AlipayRootCertPath))
+                {
+                    return null;
+                }
+                if (certEnvironment != null) return certEnvironment;
+                certEnvironment = new CertEnvironment(MerchantCertPath, AlipayCertPath, AlipayRootCertPath);
+                return certEnvironment;
+            }
+        }
+
+    }
+}

+ 21 - 0
PaySharp.Alipay/PaySharp.Alipay.csproj

@@ -0,0 +1,21 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>netcoreapp3.1</TargetFrameworks>
+    <Title>PaySharp.Alipay</Title>
+    <Description>支付宝支付</Description>
+    <PackageTags>dotnetcore;pay;alipay;</PackageTags>
+    <PackageReleaseNotes>
+      支持.net core3.1
+    </PackageReleaseNotes>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="BouncyCastle.NetCore" Version="1.8.8" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\PaySharp.Core\PaySharp.Core.csproj" />
+  </ItemGroup>
+
+</Project>

二進制
PaySharp.Alipay/PaySharp.snk


+ 57 - 0
PaySharp.Alipay/PaySharpConfigExtensions.cs

@@ -0,0 +1,57 @@
+#if NET45
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Configuration;
+using PaySharp.Alipay;
+
+namespace PaySharp.Core.Mvc
+{
+    public static class PaySharpConfigExtensions
+    {
+        public static IGateways RegisterAlipay(this IGateways gateways, Action<Merchant> action)
+        {
+            if (action != null)
+            {
+                var merchant = new Merchant();
+                action(merchant);
+                gateways.Add(new AlipayGateway(merchant));
+            }
+
+            return gateways;
+        }
+
+        public static IGateways RegisterAlipay(this IGateways gateways)
+        {
+            var merchants = (List<Hashtable>)ConfigurationManager.GetSection("paySharp/alipays");
+            if (merchants == null)
+            {
+                return gateways;
+            }
+
+            foreach (var item in merchants)
+            {
+                var alipayGateway = new AlipayGateway(new Merchant
+                {
+                    AppId = item["appId"].ToString(),
+                    AlipayPublicKey = item["alipayPublicKey"].ToString(),
+                    NotifyUrl = item["notifyUrl"].ToString(),
+                    Privatekey = item["privatekey"].ToString(),
+                    ReturnUrl = item["returnUrl"].ToString()
+                });
+
+                var gatewayUrl = item["gatewayUrl"].ToString();
+                if (!string.IsNullOrEmpty(gatewayUrl))
+                {
+                    alipayGateway.GatewayUrl = gatewayUrl;
+                }
+
+                gateways.Add(alipayGateway);
+            }
+
+            return gateways;
+        }
+    }
+}
+
+#endif

+ 13 - 0
PaySharp.Alipay/Request/AppPayRequest.cs

@@ -0,0 +1,13 @@
+using PaySharp.Alipay.Domain;
+using PaySharp.Alipay.Response;
+
+namespace PaySharp.Alipay.Request
+{
+    public class AppPayRequest : BaseRequest<AppPayModel, AppPayResponse>
+    {
+        public AppPayRequest()
+            : base("alipay.trade.app.pay")
+        {
+        }
+    }
+}

+ 49 - 0
PaySharp.Alipay/Request/BarcodePayRequest.cs

@@ -0,0 +1,49 @@
+using System;
+using PaySharp.Alipay.Domain;
+using PaySharp.Alipay.Response;
+using PaySharp.Core.Response;
+
+namespace PaySharp.Alipay.Request
+{
+    public class BarcodePayRequest : BaseRequest<BarcodePayModel, BarcodePayResponse>
+    {
+        public BarcodePayRequest()
+            : base("alipay.trade.pay")
+        {
+        }
+
+        /// <summary>
+        /// 轮询间隔,单位毫秒
+        /// </summary>
+        public int PollTime { get; set; } = 5000;
+
+        /// <summary>
+        /// 轮询次数
+        /// </summary>
+        public int PollCount { get; set; } = 5;
+
+        /// <summary>
+        /// 支付失败事件
+        /// </summary>
+        /// <param name="response">返回结果</param>
+        /// <param name="message">提示信息</param>
+        internal void OnPayFailed(IResponse response, string message) => PayFailed?.Invoke(response, message);
+
+        /// <summary>
+        /// 支付成功事件
+        /// </summary>
+        /// <param name="response">返回结果</param>
+        /// <param name="message">提示信息</param>
+        internal void OnPaySucceed(IResponse response, string message) => PaySucceed?.Invoke(response, message);
+
+        /// <summary>
+        /// 网关同步返回的支付通知验证失败时触发
+        /// </summary>
+        public event Action<IResponse, string> PayFailed;
+
+        /// <summary>
+        /// 网关同步返回的支付通知验证成功时触发
+        /// </summary>
+        public event Action<IResponse, string> PaySucceed;
+    }
+}

+ 21 - 0
PaySharp.Alipay/Request/BaseRequest.cs

@@ -0,0 +1,21 @@
+using PaySharp.Core.Request;
+using PaySharp.Core.Response;
+
+namespace PaySharp.Alipay.Request
+{
+    public class BaseRequest<TModel, TResponse> : Request<TModel, TResponse> where TResponse : IResponse
+    {
+        public BaseRequest(string method)
+        {
+            RequestUrl = "/gateway.do?charset=UTF-8";
+            GatewayData.Add("method", method);
+        }
+
+        public override void AddGatewayData(TModel model)
+        {
+            base.AddGatewayData(model);
+
+            GatewayData.Add("biz_content", PaySharp.Core.Utils.Util.SerializeObject(model));
+        }
+    }
+}

+ 13 - 0
PaySharp.Alipay/Request/BillDownloadRequest.cs

@@ -0,0 +1,13 @@
+using PaySharp.Alipay.Domain;
+using PaySharp.Alipay.Response;
+
+namespace PaySharp.Alipay.Request
+{
+    public class BillDownloadRequest : BaseRequest<BillDownloadModel, BillDownloadResponse>
+    {
+        public BillDownloadRequest()
+            : base("alipay.data.dataservice.bill.downloadurl.query")
+        {
+        }
+    }
+}

+ 13 - 0
PaySharp.Alipay/Request/CancelRequest.cs

@@ -0,0 +1,13 @@
+using PaySharp.Alipay.Domain;
+using PaySharp.Alipay.Response;
+
+namespace PaySharp.Alipay.Request
+{
+    public class CancelRequest : BaseRequest<CancelModel, CancelResponse>
+    {
+        public CancelRequest()
+            : base("alipay.trade.cancel")
+        {
+        }
+    }
+}

+ 13 - 0
PaySharp.Alipay/Request/CloseRequest.cs

@@ -0,0 +1,13 @@
+using PaySharp.Alipay.Domain;
+using PaySharp.Alipay.Response;
+
+namespace PaySharp.Alipay.Request
+{
+    public class CloseRequest : BaseRequest<CloseModel, CloseResponse>
+    {
+        public CloseRequest()
+            : base("alipay.trade.close")
+        {
+        }
+    }
+}

+ 13 - 0
PaySharp.Alipay/Request/QueryRequest.cs

@@ -0,0 +1,13 @@
+using PaySharp.Alipay.Domain;
+using PaySharp.Alipay.Response;
+
+namespace PaySharp.Alipay.Request
+{
+    public class QueryRequest : BaseRequest<QueryModel, QueryResponse>
+    {
+        public QueryRequest()
+            : base("alipay.trade.query")
+        {
+        }
+    }
+}

+ 13 - 0
PaySharp.Alipay/Request/RefundQueryRequest.cs

@@ -0,0 +1,13 @@
+using PaySharp.Alipay.Domain;
+using PaySharp.Alipay.Response;
+
+namespace PaySharp.Alipay.Request
+{
+    public class RefundQueryRequest : BaseRequest<RefundQueryModel, RefundQueryResponse>
+    {
+        public RefundQueryRequest()
+            : base("alipay.trade.fastpay.refund.query")
+        {
+        }
+    }
+}

+ 13 - 0
PaySharp.Alipay/Request/RefundRequest.cs

@@ -0,0 +1,13 @@
+using PaySharp.Alipay.Domain;
+using PaySharp.Alipay.Response;
+
+namespace PaySharp.Alipay.Request
+{
+    public class RefundRequest : BaseRequest<RefundModel, RefundResponse>
+    {
+        public RefundRequest()
+            : base("alipay.trade.refund")
+        {
+        }
+    }
+}

+ 13 - 0
PaySharp.Alipay/Request/ScanPayRequest.cs

@@ -0,0 +1,13 @@
+using PaySharp.Alipay.Domain;
+using PaySharp.Alipay.Response;
+
+namespace PaySharp.Alipay.Request
+{
+    public class ScanPayRequest : BaseRequest<ScanPayModel, ScanPayResponse>
+    {
+        public ScanPayRequest()
+            : base("alipay.trade.precreate")
+        {
+        }
+    }
+}

+ 13 - 0
PaySharp.Alipay/Request/TransferQueryRequest.cs

@@ -0,0 +1,13 @@
+using PaySharp.Alipay.Domain;
+using PaySharp.Alipay.Response;
+
+namespace PaySharp.Alipay.Request
+{
+    public class TransferQueryRequest : BaseRequest<TransferQueryModel, TransferQueryResponse>
+    {
+        public TransferQueryRequest()
+            : base("alipay.fund.trans.order.query")
+        {
+        }
+    }
+}

+ 13 - 0
PaySharp.Alipay/Request/TransferRequest.cs

@@ -0,0 +1,13 @@
+using PaySharp.Alipay.Domain;
+using PaySharp.Alipay.Response;
+
+namespace PaySharp.Alipay.Request
+{
+    public class TransferRequest : BaseRequest<TransferModel, TransferResponse>
+    {
+        public TransferRequest()
+            : base("alipay.fund.trans.toaccount.transfer")
+        {
+        }
+    }
+}

+ 13 - 0
PaySharp.Alipay/Request/WapPayRequest.cs

@@ -0,0 +1,13 @@
+using PaySharp.Alipay.Domain;
+using PaySharp.Alipay.Response;
+
+namespace PaySharp.Alipay.Request
+{
+    public class WapPayRequest : BaseRequest<WapPayModel, WapPayResponse>
+    {
+        public WapPayRequest()
+            : base("alipay.trade.wap.pay")
+        {
+        }
+    }
+}

+ 13 - 0
PaySharp.Alipay/Request/WebPayRequest.cs

@@ -0,0 +1,13 @@
+using PaySharp.Alipay.Domain;
+using PaySharp.Alipay.Response;
+
+namespace PaySharp.Alipay.Request
+{
+    public class WebPayRequest : BaseRequest<WebPayModel, WebPayResponse>
+    {
+        public WebPayRequest()
+            : base("alipay.trade.page.pay")
+        {
+        }
+    }
+}

+ 20 - 0
PaySharp.Alipay/Response/AppPayResponse.cs

@@ -0,0 +1,20 @@
+using PaySharp.Alipay.Request;
+using PaySharp.Core.Response;
+
+namespace PaySharp.Alipay.Response
+{
+    public class AppPayResponse : IResponse
+    {
+        public AppPayResponse(AppPayRequest request)
+        {
+            OrderInfo = request.GatewayData.ToUrl();
+        }
+
+        /// <summary>
+        /// 用于唤起App的订单参数
+        /// </summary>
+        public string OrderInfo { get; set; }
+
+        public string Raw { get; set; }
+    }
+}

+ 241 - 0
PaySharp.Alipay/Response/BarcodePayResponse.cs

@@ -0,0 +1,241 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using PaySharp.Alipay.Domain;
+using PaySharp.Alipay.Request;
+using PaySharp.Core.Request;
+
+namespace PaySharp.Alipay.Response
+{
+    public class BarcodePayResponse : BaseResponse
+    {
+        /// <summary>
+        /// 支付宝交易号
+        /// </summary>
+        public string TradeNo { get; set; }
+
+        /// <summary>
+        /// 商户订单号
+        /// </summary>
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 买家支付宝账号
+        /// </summary>
+        public string BuyerLogonId { get; set; }
+
+        /// <summary>
+        /// 订单金额
+        /// </summary>
+        public double TotalAmount { get; set; }
+
+        /// <summary>
+        /// 标价币种
+        /// </summary>
+        public string TransCurrency { get; set; }
+
+        /// <summary>
+        /// 结算币种
+        /// </summary>
+        public string SettleCurrency { get; set; }
+
+        /// <summary>
+        /// 结算金额
+        /// </summary>
+        public double SettleAmount { get; set; }
+
+        /// <summary>
+        /// 支付币种
+        /// </summary>
+        public string PayCurrency { get; set; }
+
+        /// <summary>
+        /// 支付金额
+        /// </summary>
+        public double PayAmount { get; set; }
+
+        /// <summary>
+        /// 结算币种兑换标价币种汇率
+        /// </summary>
+        public double SettleTransRate { get; set; }
+
+        /// <summary>
+        /// 标价币种兑换支付币种汇率
+        /// </summary>
+        public double TransPayRate { get; set; }
+
+        /// <summary>
+        /// 实收金额
+        /// </summary>
+        public double ReceiptAmount { get; set; }
+
+        /// <summary>
+        /// 付款金额
+        /// </summary>
+        public double BuyerPayAmount { get; set; }
+
+        /// <summary>
+        /// 集分宝金额
+        /// </summary>
+        public double PointAmount { get; set; }
+
+        /// <summary>
+        /// 开票金额
+        /// </summary>
+        public double InvoiceAmount { get; set; }
+
+        /// <summary>
+        /// 交易付款时间
+        /// </summary>
+        public DateTime GmtPayment { get; set; }
+
+        /// <summary>
+        /// 交易支付使用的资金渠道
+        /// </summary>
+        public List<TradeFundBill> FundBillList { get; set; }
+
+        /// <summary>
+        /// 支付宝卡余额
+        /// </summary>
+        public double CardBalance { get; set; }
+
+        /// <summary>
+        /// 发生支付交易的商户门店名称
+        /// </summary>
+        public string StoreName { get; set; }
+
+        /// <summary>
+        /// 买家支付宝用户号
+        /// </summary>
+        public string BuyerUserId { get; set; }
+
+        /// <summary>
+        /// 本交易支付时使用的所有优惠券信息
+        /// </summary>
+        public string DiscountGoodsDetail { get; set; }
+
+        /// <summary>
+        /// 本交易支付时使用的所有优惠券信息
+        /// </summary>
+        public List<VoucherDetail> VoucherDetailList { get; set; }
+
+        /// <summary>
+        /// 预授权支付模式,该参数仅在信用预授权支付场景下返回。信用预授权支付:CREDIT_PREAUTH_PAY
+        /// </summary>
+        public string AuthTradePayMode { get; set; }
+
+        /// <summary>
+        /// 商户传入业务信息,具体值要和支付宝约定 
+        /// 将商户传入信息分发给相应系统,应用于安全,营销等参数直传场景
+        /// 格式为json格式
+        /// </summary>
+        public string BusinessParams { get; set; }
+
+        /// <summary>
+        /// 买家用户类型
+        /// CORPORATE:企业用户
+        /// PRIVATE:个人用户
+        /// </summary>
+        public string BuyerUserType { get; set; }
+
+        /// <summary>
+        /// 商家优惠金额
+        /// </summary>
+        public double MdiscountAmount { get; set; }
+
+        /// <summary>
+        /// 平台优惠金额
+        /// </summary>
+        public double DiscountAmount { get; set; }
+
+        private Merchant _merchant;
+
+        internal override void Execute<TModel, TResponse>(Merchant merchant, Request<TModel, TResponse> request)
+        {
+            _merchant = merchant;
+            var barcodePayRequest = request as BarcodePayRequest;
+
+            if (Code == "10000")
+            {
+                barcodePayRequest.OnPaySucceed(this, null);
+                return;
+            }
+
+            if (!string.IsNullOrEmpty(TradeNo))
+            {
+                var queryResponse = new QueryResponse();
+                Task.Run(async () =>
+                {
+                    queryResponse = await PollQueryTradeStateAsync(
+                        TradeNo,
+                        barcodePayRequest.PollTime,
+                        barcodePayRequest.PollCount);
+                })
+                .GetAwaiter()
+                .GetResult();
+
+                if (queryResponse != null)
+                {
+                    barcodePayRequest.OnPaySucceed(queryResponse, null);
+                    return;
+                }
+                else
+                {
+                    barcodePayRequest.OnPayFailed(this, "支付超时");
+                    return;
+                }
+            }
+
+            barcodePayRequest.OnPayFailed(this, SubMessage);
+        }
+
+        /// <summary>
+        /// 轮询查询用户是否支付
+        /// </summary>
+        /// <param name="tradeNo">支付宝订单号</param>
+        /// <param name="pollTime">轮询间隔</param>
+        /// <param name="pollCount">轮询次数</param>
+        /// <returns></returns>
+        private QueryResponse PollQueryTradeState(string tradeNo, int pollTime, int pollCount)
+        {
+            for (var i = 0; i < pollCount; i++)
+            {
+                var queryRequest = new QueryRequest();
+                queryRequest.AddGatewayData(new QueryModel
+                {
+                    TradeNo = tradeNo
+                });
+                var queryResponse = SubmitProcess.Execute(_merchant, queryRequest);
+                if (queryResponse.TradeStatus == "TRADE_SUCCESS")
+                {
+                    return queryResponse;
+                }
+
+                Thread.Sleep(pollTime);
+            }
+
+            //支付超时,取消订单
+            var cancelRequest = new CancelRequest();
+            cancelRequest.AddGatewayData(new CancelModel
+            {
+                TradeNo = tradeNo
+            });
+            SubmitProcess.Execute(_merchant, cancelRequest);
+
+            return null;
+        }
+
+        /// <summary>
+        /// 轮询查询用户是否支付
+        /// </summary>
+        /// <param name="tradeNo">支付宝订单号</param>
+        /// <param name="pollTime">轮询间隔</param>
+        /// <param name="pollCount">轮询次数</param>
+        /// <returns></returns>
+        private async Task<QueryResponse> PollQueryTradeStateAsync(string tradeNo, int pollTime, int pollCount)
+        {
+            return await Task.Run(() => PollQueryTradeState(tradeNo, pollTime, pollCount));
+        }
+    }
+}

+ 49 - 0
PaySharp.Alipay/Response/BaseResponse.cs

@@ -0,0 +1,49 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+using PaySharp.Core.Request;
+using PaySharp.Core.Response;
+
+namespace PaySharp.Alipay.Response
+{
+    [JsonObject(NamingStrategyType = typeof(SnakeCaseNamingStrategy))]
+    public abstract class BaseResponse : IResponse
+    {
+        /// <summary>
+        /// 网关返回码,详见文档
+        /// https://docs.open.alipay.com/common/105806
+        /// </summary>
+        public string Code { get; set; }
+
+        /// <summary>
+        /// 网关返回码描述,详见文档
+        /// https://docs.open.alipay.com/common/105806
+        /// </summary>
+        [JsonProperty("msg")]
+        public string Message { get; set; }
+
+        /// <summary>
+        /// 网关返回码,详见文档
+        /// https://docs.open.alipay.com/common/105806
+        /// </summary>
+        public string SubCode { get; set; }
+
+        /// <summary>
+        /// 网关返回码描述,详见文档
+        /// https://docs.open.alipay.com/common/105806
+        /// </summary>
+        [JsonProperty("sub_msg")]
+        public string SubMessage { get; set; }
+
+        /// <summary>
+        /// 签名
+        /// </summary>
+        public string Sign { get; set; }
+
+        /// <summary>
+        /// 原始值
+        /// </summary>
+        public string Raw { get; set; }
+
+        internal abstract void Execute<TModel, TResponse>(Merchant merchant, Request<TModel, TResponse> request) where TResponse : IResponse;
+    }
+}

+ 46 - 0
PaySharp.Alipay/Response/BillDownloadResponse.cs

@@ -0,0 +1,46 @@
+using System.Threading.Tasks;
+using PaySharp.Core.Request;
+using PaySharp.Core.Utils;
+
+namespace PaySharp.Alipay.Response
+{
+    public class BillDownloadResponse : BaseResponse
+    {
+        /// <summary>
+        /// 账单下载地址链接,获取连接后30秒后未下载,链接地址失效。
+        /// </summary>
+        public string BillDownloadUrl { get; set; }
+
+        private byte[] _billFile;
+
+        /// <summary>
+        /// 获取账单文件
+        /// </summary>
+        public byte[] GetBillFile()
+        {
+            if (_billFile == null && !string.IsNullOrEmpty(BillDownloadUrl))
+            {
+                _billFile = HttpUtil.Download(BillDownloadUrl);
+            }
+
+            return _billFile;
+        }
+
+        /// <summary>
+        /// 获取账单文件
+        /// </summary>
+        public async Task<byte[]> GetBillFileAsync()
+        {
+            if (_billFile == null && !string.IsNullOrEmpty(BillDownloadUrl))
+            {
+                _billFile = await HttpUtil.DownloadAsync(BillDownloadUrl);
+            }
+
+            return _billFile;
+        }
+
+        internal override void Execute<TModel, TResponse>(Merchant merchant, Request<TModel, TResponse> request)
+        {
+        }
+    }
+}

+ 33 - 0
PaySharp.Alipay/Response/CancelResponse.cs

@@ -0,0 +1,33 @@
+using PaySharp.Core.Request;
+
+namespace PaySharp.Alipay.Response
+{
+    public class CancelResponse : BaseResponse
+    {
+        /// <summary>
+        /// 支付宝交易号
+        /// </summary>
+        public string TradeNo { get; set; }
+
+        /// <summary>
+        /// 商户订单号
+        /// </summary>
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 是否需要重试
+        /// </summary>
+        public string RetryFlag { get; set; }
+
+        /// <summary>
+        /// 本次撤销触发的交易动作 
+        /// close:关闭交易,无退款
+        /// refund:产生了退款
+        /// </summary>
+        public string Action { get; set; }
+
+        internal override void Execute<TModel, TResponse>(Merchant merchant, Request<TModel, TResponse> request)
+        {
+        }
+    }
+}

+ 21 - 0
PaySharp.Alipay/Response/CloseResponse.cs

@@ -0,0 +1,21 @@
+using PaySharp.Core.Request;
+
+namespace PaySharp.Alipay.Response
+{
+    public class CloseResponse : BaseResponse
+    {
+        /// <summary>
+        /// 支付宝交易号
+        /// </summary>
+        public string TradeNo { get; set; }
+
+        /// <summary>
+        /// 商户订单号
+        /// </summary>
+        public string OutTradeNo { get; set; }
+
+        internal override void Execute<TModel, TResponse>(Merchant merchant, Request<TModel, TResponse> request)
+        {
+        }
+    }
+}

+ 178 - 0
PaySharp.Alipay/Response/NotifyResponse.cs

@@ -0,0 +1,178 @@
+using System;
+using PaySharp.Core;
+using PaySharp.Core.Response;
+
+namespace PaySharp.Alipay.Response
+{
+    public class NotifyResponse : IResponse
+    {
+        /// <summary>
+        /// 通知时间
+        /// </summary>
+        public DateTime NotifyTime { get; set; }
+
+        /// <summary>
+        /// 通知类型
+        /// </summary>
+        public string NotifyType { get; set; }
+
+        /// <summary>
+        /// 通知校验ID
+        /// </summary>
+        public string NotifyId { get; set; }
+
+        /// <summary>
+        /// 开发者的app_id
+        /// </summary>
+        public string AppId { get; set; }
+
+        /// <summary>
+        /// 编码格式
+        /// </summary>
+        public string Charset { get; set; }
+
+        /// <summary>
+        /// 接口版本
+        /// </summary>
+        public string Version { get; set; }
+
+        /// <summary>
+        /// 签名类型
+        /// </summary>
+        public string SignType { get; set; }
+
+        /// <summary>
+        /// 签名
+        /// </summary>
+        public string Sign { get; set; }
+
+        /// <summary>
+        /// 授权方的app_id
+        /// </summary>
+        public string AuthAppId { get; set; }
+
+        /// <summary>
+        /// 支付宝交易号
+        /// </summary>
+        public string TradeNo { get; set; }
+
+        /// <summary>
+        /// 商户订单号
+        /// </summary>
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 商户业务号
+        /// </summary>
+        public string OutBizNo { get; set; }
+
+        /// <summary>
+        /// 买家支付宝用户号
+        /// </summary>
+        public string BuyerId { get; set; }
+
+        /// <summary>
+        /// 买家支付宝账号
+        /// </summary>
+        public string BuyerLogonId { get; set; }
+
+        /// <summary>
+        /// 卖家支付宝用户号
+        /// </summary>
+        public string SellerId { get; set; }
+
+        /// <summary>
+        /// 卖家支付宝账号
+        /// </summary>
+        public string SellerEmail { get; set; }
+
+        /// <summary>
+        /// 交易状态
+        /// </summary>
+        public string TradeStatus { get; set; }
+
+        /// <summary>
+        /// 订单金额
+        /// </summary>
+        public double TotalAmount { get; set; }
+
+        /// <summary>
+        /// 实收金额
+        /// </summary>
+        public double ReceiptAmount { get; set; }
+
+        /// <summary>
+        /// 开票金额
+        /// </summary>
+        public double InvoiceAmount { get; set; }
+
+        /// <summary>
+        /// 付款金额
+        /// </summary>
+        public double BuyerPayAmount { get; set; }
+
+        /// <summary>
+        /// 集分宝金额
+        /// </summary>
+        public double PointAmount { get; set; }
+
+        /// <summary>
+        /// 总退款金额
+        /// </summary>
+        [ReName(Name = "refund_fee")]
+        public double RefundAmount { get; set; }
+
+        /// <summary>
+        /// 实际退款金额
+        /// </summary>
+        [ReName(Name = "send_back_fee")]
+        public double SendBackAmount { get; set; }
+
+        /// <summary>
+        /// 订单标题
+        /// </summary>
+        public string Subject { get; set; }
+
+        /// <summary>
+        /// 商品描述
+        /// </summary>
+        public string Body { get; set; }
+
+        /// <summary>
+        /// 交易创建时间
+        /// </summary>
+        public DateTime GmtCreate { get; set; }
+
+        /// <summary>
+        /// 交易付款时间
+        /// </summary>
+        public DateTime GmtPayment { get; set; }
+
+        /// <summary>
+        /// 交易退款时间
+        /// </summary>
+        public DateTime GmtRefund { get; set; }
+
+        /// <summary>
+        /// 交易结束时间
+        /// </summary>
+        public DateTime GmtClose { get; set; }
+
+        /// <summary>
+        /// 支付金额信息
+        /// </summary>
+        public string FundBillList { get; set; }
+
+        /// <summary>
+        /// 回传参数
+        /// </summary>
+        public string PassbackParams { get; set; }
+
+        /// <summary>
+        /// 优惠券信息
+        /// </summary>
+        public string VoucherDetailList { get; set; }
+
+        public string Raw { get; set; }
+    }
+}

+ 146 - 0
PaySharp.Alipay/Response/QueryResponse.cs

@@ -0,0 +1,146 @@
+using System;
+using System.Collections.Generic;
+using PaySharp.Alipay.Domain;
+using PaySharp.Core.Request;
+
+namespace PaySharp.Alipay.Response
+{
+    public class QueryResponse : BaseResponse
+    {
+        /// <summary>
+        /// 支付宝交易号
+        /// </summary>
+        public string TradeNo { get; set; }
+
+        /// <summary>
+        /// 商户订单号
+        /// </summary>
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 买家支付宝账号
+        /// </summary>
+        public string BuyerLogonId { get; set; }
+
+        /// <summary>
+        /// 交易状态
+        /// </summary>
+        public string TradeStatus { get; set; }
+
+        /// <summary>
+        /// 订单金额
+        /// </summary>
+        public double TotalAmount { get; set; }
+
+        /// <summary>
+        /// 标价币种
+        /// </summary>
+        public string TransCurrency { get; set; }
+
+        /// <summary>
+        /// 结算币种
+        /// </summary>
+        public string SettleCurrency { get; set; }
+
+        /// <summary>
+        /// 结算金额
+        /// </summary>
+        public double SettleAmount { get; set; }
+
+        /// <summary>
+        /// 支付币种
+        /// </summary>
+        public string PayCurrency { get; set; }
+
+        /// <summary>
+        /// 支付金额
+        /// </summary>
+        public double PayAmount { get; set; }
+
+        /// <summary>
+        /// 结算币种兑换标价币种汇率
+        /// </summary>
+        public double SettleTransRate { get; set; }
+
+        /// <summary>
+        /// 标价币种兑换支付币种汇率
+        /// </summary>
+        public double TransPayRate { get; set; }
+
+        /// <summary>
+        /// 付款金额
+        /// </summary>
+        public double BuyerPayAmount { get; set; }
+
+        /// <summary>
+        /// 集分宝金额
+        /// </summary>
+        public double PointAmount { get; set; }
+
+        /// <summary>
+        /// 开票金额
+        /// </summary>
+        public double InvoiceAmount { get; set; }
+
+        /// <summary>
+        /// 本次交易打款给卖家的时间
+        /// </summary>
+        public DateTime SendPayDate { get; set; }
+
+        /// <summary>
+        /// 实收金额
+        /// </summary>
+        public double ReceiptAmount { get; set; }
+
+        /// <summary>
+        /// 商户门店编号
+        /// </summary>
+        public string StoreId { get; set; }
+
+        /// <summary>
+        /// 商户机具终端编号
+        /// </summary>
+        public string TerminalId { get; set; }
+
+        /// <summary>
+        /// 交易支付使用的资金渠道
+        /// </summary>
+        public List<TradeFundBill> FundBillList { get; set; }
+
+        /// <summary>
+        /// 发生支付交易的商户门店名称
+        /// </summary>
+        public string StoreName { get; set; }
+
+        /// <summary>
+        /// 买家支付宝用户号
+        /// </summary>
+        public string BuyerUserId { get; set; }
+
+        /// <summary>
+        /// 预授权支付模式,该参数仅在信用预授权支付场景下返回。信用预授权支付:CREDIT_PREAUTH_PAY
+        /// </summary>
+        public string AuthTradePayMode { get; set; }
+
+        /// <summary>
+        /// 买家用户类型
+        /// CORPORATE:企业用户
+        /// PRIVATE:个人用户
+        /// </summary>
+        public string BuyerUserType { get; set; }
+
+        /// <summary>
+        /// 商家优惠金额
+        /// </summary>
+        public double MdiscountAmount { get; set; }
+
+        /// <summary>
+        /// 平台优惠金额
+        /// </summary>
+        public double DiscountAmount { get; set; }
+
+        internal override void Execute<TModel, TResponse>(Merchant merchant, Request<TModel, TResponse> request)
+        {
+        }
+    }
+}

+ 43 - 0
PaySharp.Alipay/Response/RefundQueryResponse.cs

@@ -0,0 +1,43 @@
+using Newtonsoft.Json;
+using PaySharp.Core.Request;
+
+namespace PaySharp.Alipay.Response
+{
+    public class RefundQueryResponse : BaseResponse
+    {
+        /// <summary>
+        /// 支付宝交易号
+        /// </summary>
+        public string TradeNo { get; set; }
+
+        /// <summary>
+        /// 商户订单号
+        /// </summary>
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 本笔退款对应的退款请求号
+        /// </summary>
+        [JsonProperty("out_request_no")]
+        public string OutRefundNo { get; set; }
+
+        /// <summary>
+        /// 发起退款时,传入的退款原因
+        /// </summary>
+        public string RefundReason { get; set; }
+
+        /// <summary>
+        /// 该笔退款所对应的交易的订单金额
+        /// </summary>
+        public double TotalAmount { get; set; }
+
+        /// <summary>
+        /// 退款金额
+        /// </summary>
+        public double RefundAmount { get; set; }
+
+        internal override void Execute<TModel, TResponse>(Merchant merchant, Request<TModel, TResponse> request)
+        {
+        }
+    }
+}

+ 85 - 0
PaySharp.Alipay/Response/RefundResponse.cs

@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using PaySharp.Alipay.Domain;
+using PaySharp.Core.Request;
+
+namespace PaySharp.Alipay.Response
+{
+    public class RefundResponse : BaseResponse
+    {
+        /// <summary>
+        /// 支付宝交易号
+        /// </summary>
+        public string TradeNo { get; set; }
+
+        /// <summary>
+        /// 商户订单号
+        /// </summary>
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 买家支付宝账号
+        /// </summary>
+        public string BuyerLogonId { get; set; }
+
+        /// <summary>
+        /// 本次退款是否发生了资金变化
+        /// </summary>
+        public string FundChange { get; set; }
+
+        /// <summary>
+        /// 退款总金额
+        /// </summary>
+        [JsonProperty("refund_fee")]
+        public double RefundAmount { get; set; }
+
+        /// <summary>
+        /// 退款币种信息
+        /// </summary>
+        public string RefundCurrency { get; set; }
+
+        /// <summary>
+        /// 退款支付时间
+        /// </summary>
+        public DateTime GmtRefundPay { get; set; }
+
+        /// <summary>
+        /// 退款使用的资金渠道
+        /// </summary>
+        [JsonProperty("refund_detail_item_list")]
+        public List<TradeFundBill> FundBillList { get; set; }
+
+        /// <summary>
+        /// 发生支付交易的商户门店名称
+        /// </summary>
+        public string StoreName { get; set; }
+
+        /// <summary>
+        /// 买家支付宝用户号
+        /// </summary>
+        public string BuyerUserId { get; set; }
+
+        /// <summary>
+        /// 本次退款金额中买家退款金额
+        /// </summary>
+        [JsonProperty("present_refund_buyer_amount")]
+        public double RefundBuyerAmount { get; set; }
+
+        /// <summary>
+        /// 本次退款金额中平台优惠退款金额
+        /// </summary>
+        [JsonProperty("present_refund_discount_amount")]
+        public double RefundDiscountAmount { get; set; }
+
+        /// <summary>
+        /// 本次退款金额中商家优惠退款金额
+        /// </summary>
+        [JsonProperty("present_refund_mdiscount_amount")]
+        public double RefundMdiscountAmount { get; set; }
+
+        internal override void Execute<TModel, TResponse>(Merchant merchant, Request<TModel, TResponse> request)
+        {
+        }
+    }
+}

+ 21 - 0
PaySharp.Alipay/Response/ScanPayResponse.cs

@@ -0,0 +1,21 @@
+using PaySharp.Core.Request;
+
+namespace PaySharp.Alipay.Response
+{
+    public class ScanPayResponse : BaseResponse
+    {
+        /// <summary>
+        /// 商户订单号
+        /// </summary>
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 当前预下单请求生成的二维码码串,可以用二维码生成工具根据该码串值生成对应的二维码
+        /// </summary>
+        public string QrCode { get; set; }
+
+        internal override void Execute<TModel, TResponse>(Merchant merchant, Request<TModel, TResponse> request)
+        {
+        }
+    }
+}

+ 65 - 0
PaySharp.Alipay/Response/TransferQueryResponse.cs

@@ -0,0 +1,65 @@
+using System;
+using Newtonsoft.Json;
+using PaySharp.Core.Request;
+
+namespace PaySharp.Alipay.Response
+{
+    public class TransferQueryResponse : BaseResponse
+    {
+        /// <summary>
+        /// 支付宝转账单据号,查询失败不返回
+        /// </summary>
+        [JsonProperty("order_id")]
+        public string TradeNo { get; set; }
+
+        /// <summary>
+        /// 发起转账来源方定义的转账单据号。 
+        /// 该参数的赋值均以查询结果中 的 out_biz_no 为准。 
+        /// 如果查询失败,不返回该参数。
+        /// </summary>
+        [JsonProperty("out_biz_no")]
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 转账单据状态。 
+        /// SUCCESS:成功(配合"单笔转账到银行账户接口"产品使用时, 同一笔单据多次查询有可能从成功变成退票状态); 
+        /// FAIL:失败(具体失败原因请参见error_code以及fail_reason返回值); 
+        /// INIT:等待处理; 
+        /// DEALING:处理中; 
+        /// REFUND:退票(仅配合"单笔转账到银行账户接口"产品使用时会涉及, 具体退票原因请参见fail_reason返回值); 
+        /// UNKNOWN:状态未知。
+        /// </summary>
+        public string Status { get; set; }
+
+        /// <summary>
+        /// 支付时间:格式为yyyy-MM-dd HH:mm:ss,仅转账成功返回。
+        /// </summary>
+        public DateTime? PayDate { get; set; }
+
+        /// <summary>
+        /// 预计到账时间,转账到银行卡专用,格式为yyyy-MM-dd HH:mm:ss,转账受理失败不返回。 
+        /// 注意:此参数为预计时间,可能与实际到账时间有较大误差,不能作为实际到账时间使用,仅供参考用途。
+        /// </summary>
+        public DateTime? ArrivalTimeEnd { get; set; }
+
+        /// <summary>
+        /// 预计收费金额(元),转账到银行卡专用,数字格式,精确到小数点后2位,转账失败或转账受理失败不返回。
+        /// </summary>
+        public double OrderFee { get; set; }
+
+        /// <summary>
+        /// 查询到的订单状态为FAIL失败或REFUND退票时,返回具体的原因。
+        /// </summary>
+        public string FailReason { get; set; }
+
+        /// <summary>
+        /// 查询失败时,本参数为错误代码。 
+        /// 查询成功不返回。 对于退票订单,不返回该参数。
+        /// </summary>
+        public string ErrorCode { get; set; }
+
+        internal override void Execute<TModel, TResponse>(Merchant merchant, Request<TModel, TResponse> request)
+        {
+        }
+    }
+}

+ 32 - 0
PaySharp.Alipay/Response/TransferResponse.cs

@@ -0,0 +1,32 @@
+using System;
+using Newtonsoft.Json;
+using PaySharp.Core.Request;
+
+namespace PaySharp.Alipay.Response
+{
+    public class TransferResponse : BaseResponse
+    {
+        /// <summary>
+        /// 支付宝转账单据号,成功一定返回,失败可能不返回也可能返回。
+        /// </summary>
+        [JsonProperty("order_id")]
+        public string TradeNo { get; set; }
+
+        /// <summary>
+        /// 商户转账唯一订单号。发起转账来源方定义的转账单据ID,用于将转账回执通知给来源方。 
+        /// 不同来源方给出的ID可以重复,同一个来源方必须保证其ID的唯一性。 
+        /// 只支持半角英文、数字,及“-”、“_”。
+        /// </summary>
+        [JsonProperty("out_biz_no")]
+        public string OutTradeNo { get; set; }
+
+        /// <summary>
+        /// 支付时间:格式为yyyy-MM-dd HH:mm:ss,仅转账成功返回。
+        /// </summary>
+        public DateTime? PayDate { get; set; }
+
+        internal override void Execute<TModel, TResponse>(Merchant merchant, Request<TModel, TResponse> request)
+        {
+        }
+    }
+}

+ 20 - 0
PaySharp.Alipay/Response/WapPayResponse.cs

@@ -0,0 +1,20 @@
+using PaySharp.Alipay.Request;
+using PaySharp.Core.Response;
+
+namespace PaySharp.Alipay.Response
+{
+    public class WapPayResponse : IResponse
+    {
+        public WapPayResponse(WapPayRequest request)
+        {
+            Url = $"{request.RequestUrl}&{request.GatewayData.ToUrl()}";
+        }
+
+        /// <summary>
+        /// 跳转链接
+        /// </summary>
+        public string Url { get; set; }
+
+        public string Raw { get; set; }
+    }
+}

+ 20 - 0
PaySharp.Alipay/Response/WebPayResponse.cs

@@ -0,0 +1,20 @@
+using PaySharp.Alipay.Request;
+using PaySharp.Core.Response;
+
+namespace PaySharp.Alipay.Response
+{
+    public class WebPayResponse : IResponse
+    {
+        public WebPayResponse(WebPayRequest request)
+        {
+            Html = request.GatewayData.ToForm(request.RequestUrl);
+        }
+
+        /// <summary>
+        /// 生成的Html网页
+        /// </summary>
+        public string Html { get; set; }
+
+        public string Raw { get; set; }
+    }
+}

+ 46 - 0
PaySharp.Alipay/ServiceCollectionExtensions.cs

@@ -0,0 +1,46 @@
+#if NETCOREAPP3_1
+using System;
+using Microsoft.Extensions.Configuration;
+using PaySharp.Alipay;
+using PaySharp.Core;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+    public static class ServiceCollectionExtensions
+    {
+        public static IGateways UseAlipay(this IGateways gateways, Action<Merchant> action)
+        {
+            if (action != null)
+            {
+                var merchant = new Merchant();
+                action(merchant);
+                gateways.Add(new AlipayGateway(merchant));
+            }
+
+            return gateways;
+        }
+
+        public static IGateways UseAlipay(this IGateways gateways, IConfiguration configuration)
+        {
+            var merchants = configuration.GetSection("PaySharp:Alipays").Get<Merchant[]>();
+            if (merchants != null)
+            {
+                for (var i = 0; i < merchants.Length; i++)
+                {
+                    var alipayGateway = new AlipayGateway(merchants[i]);
+                    var gatewayUrl = configuration.GetSection($"PaySharp:Alipays:{i}:GatewayUrl").Value;
+                    if (!string.IsNullOrEmpty(gatewayUrl))
+                    {
+                        alipayGateway.GatewayUrl = gatewayUrl;
+                    }
+
+                    gateways.Add(alipayGateway);
+                }
+            }
+
+            return gateways;
+        }
+    }
+}
+
+#endif

+ 91 - 0
PaySharp.Alipay/SubmitProcess.cs

@@ -0,0 +1,91 @@
+using System;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using PaySharp.Alipay.Response;
+using PaySharp.Core;
+using PaySharp.Core.Exceptions;
+using PaySharp.Core.Request;
+using PaySharp.Core.Response;
+using PaySharp.Core.Utils;
+
+namespace PaySharp.Alipay
+{
+    internal static class SubmitProcess
+    {
+        private static string _gatewayUrl;
+
+        internal static TResponse Execute<TModel, TResponse>(Merchant merchant, Request<TModel, TResponse> request, string gatewayUrl = null) where TResponse : IResponse
+        {
+            AddMerchant(merchant, request, gatewayUrl);
+
+            var result = HttpUtil.Post(request.RequestUrl, request.GatewayData.ToUrl());
+
+            var jObject = JObject.Parse(result);
+            var jToken = jObject.First.First;
+            var sign = jObject.Value<string>("sign");
+            if (!string.IsNullOrEmpty(sign) &&
+                !CheckSign(
+                    jToken.ToString(Formatting.None),
+                    sign,
+                    merchant.CertEnvironment == null ? merchant.AlipayPublicKey : merchant.CertEnvironment.GetAlipayPublicKey(jObject.Value<string>("alipay_cert_sn")),
+                    merchant.SignType
+                )
+            )
+            {
+                throw new GatewayException("签名验证失败");
+            }
+
+            var baseResponse = (BaseResponse)jToken.ToObject(typeof(TResponse));
+            baseResponse.Raw = result;
+            baseResponse.Sign = sign;
+            baseResponse.Execute(merchant, request);
+            return (TResponse)(object)baseResponse;
+        }
+
+        internal static TResponse SdkExecute<TModel, TResponse>(Merchant merchant, Request<TModel, TResponse> request, string gatewayUrl) where TResponse : IResponse
+        {
+            AddMerchant(merchant, request, gatewayUrl);
+
+            return (TResponse)Activator.CreateInstance(typeof(TResponse), request);
+        }
+
+        private static void AddMerchant<TModel, TResponse>(Merchant merchant, Request<TModel, TResponse> request, string gatewayUrl) where TResponse : IResponse
+        {
+            if (!string.IsNullOrEmpty(gatewayUrl))
+            {
+                _gatewayUrl = gatewayUrl;
+            }
+
+            if (!request.RequestUrl.StartsWith("http"))
+            {
+                request.RequestUrl = _gatewayUrl + request.RequestUrl;
+            }
+            request.GatewayData.Add(merchant, StringCase.Snake);
+            if (merchant.CertEnvironment != null)
+            {
+                request.GatewayData.Add("app_cert_sn", merchant.CertEnvironment.MerchantCertSN);
+                request.GatewayData.Add("alipay_root_cert_sn", merchant.CertEnvironment.RootCertSN);
+            }
+            if (!string.IsNullOrEmpty(request.NotifyUrl))
+            {
+                request.GatewayData.Add("notify_url", request.NotifyUrl);
+            }
+            if (!string.IsNullOrEmpty(request.ReturnUrl))
+            {
+                request.GatewayData.Add("return_url", request.ReturnUrl);
+            }
+            request.GatewayData.Add("sign", BuildSign(request.GatewayData, merchant.Privatekey));
+        }
+
+        internal static string BuildSign(GatewayData gatewayData, string privatekey)
+        {
+            gatewayData.Remove("sign");
+            return EncryptUtil.Sign(gatewayData.ToUrl(false), privatekey);
+        }
+
+        internal static bool CheckSign(string data, string sign, string alipayPublicKey, string signType)
+        {
+            return EncryptUtil.Verify(data, sign, alipayPublicKey);
+        }
+    }
+}

+ 716 - 0
PaySharp.Alipay/Util/AlipaySignature.cs

@@ -0,0 +1,716 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.IO;
+using Org.BouncyCastle.X509;
+using PaySharp.Alipay.Util.Asymmetric;
+
+namespace PaySharp.Alipay.Util
+{
+    /// <summary>
+    /// 支付宝签名工具类
+    /// </summary>
+    public static class AlipaySignature
+    {
+        /** 默认编码字符集 */
+        private static readonly string DEFAULT_CHARSET = "GBK";
+
+        /// <summary>
+        /// 从支付宝公钥证书中提取支付宝公钥
+        /// </summary>
+        /// <param name="certPath">证书路径</param>
+        /// <returns>公钥字符串</returns>
+        public static string ExtractPemPublicKeyFromCert(string certPath)
+        {
+            ArgumentValidator.CheckArgument(!String.IsNullOrEmpty(certPath), "证书文件路径不可为空。");
+
+            X509Certificate alipayPublicKeyCert = AntCertificationUtil.ParseCert(File.ReadAllText(certPath));
+            return AntCertificationUtil.ExtractPemPublicKeyFromCert(alipayPublicKeyCert);
+        }
+
+        /// <summary>
+        /// 异步通知参数验签,支持RSA、RSA2、SM2三种算法
+        /// V1版本方法将删除sign_type参数再进行验签,V2版本方法则不会
+        /// </summary>
+        /// <param name="parameters">待验签的参数集合</param>
+        /// <param name="alipayPublicCertPath">支付宝公钥证书路径</param>
+        /// <param name="charset">参数编码字符集</param>
+        /// <param name="signType">签名类型,RSA2或RSA、SM2</param>
+        /// <returns>true:验证成功;false:验证失败</returns>
+        public static bool CertVerifyV1(IDictionary<string, string> parameters, string alipayPublicCertPath, string charset, string signType)
+        {
+            return RSACertCheckV1(parameters, alipayPublicCertPath, charset, signType);
+        }
+
+        /// <summary>
+        /// 异步通知参数验签,支持RSA、RSA2、SM2三种算法,推荐替换为相同功能的方法CertVerifyV1
+        /// V1版本方法将删除sign_type参数再进行验签,V2版本方法则不会
+        /// </summary>
+        /// <param name="parameters">待验签的参数集合</param>
+        /// <param name="alipayPublicCertPath">支付宝公钥证书路径</param>
+        /// <param name="charset">参数编码字符集</param>
+        /// <param name="signType">签名类型,RSA2或RSA、SM2</param>
+        /// <returns>true:验证成功;false:验证失败</returns>
+        public static bool RSACertCheckV1(IDictionary<string, string> parameters, string alipayPublicCertPath, string charset, string signType)
+        {
+            ArgumentValidator.CheckNotNull(parameters, "parameters参数不可为Null");
+            ArgumentValidator.CheckArgument(!String.IsNullOrEmpty(alipayPublicCertPath), "证书文件路径不可为空。");
+
+            string alipayPublicKey = ExtractPemPublicKeyFromCert(alipayPublicCertPath);
+            return RSACheckV1(parameters, alipayPublicKey, charset, signType, false);
+        }
+
+        /// <summary>
+        /// 异步通知参数验签,支持RSA、RSA2、SM2三种算法
+        /// V1版本方法将删除sign_type参数再进行验签,V2版本方法则不会
+        /// </summary>
+        /// <param name="parameters">待验签字符串</param>
+        /// <param name="publicKey">支付宝公钥</param>
+        /// <param name="charset">参数编码字符集</param>
+        /// <param name="signType">签名类型,RSA2或RSA、SM2</param>
+        /// <param name="keyFromFile">是否从文件加载支付宝公钥内容。
+        /// 如果该参数为true,则publicKey为公钥文件路径;
+        /// 如果该参数为false,则publicKey为公钥内容
+        /// </param>
+        /// <returns>true:验证成功;false:验证失败</returns>
+        public static bool VerifyV1(IDictionary<string, string> parameters, string publicKey, string charset, string signType, bool keyFromFile)
+        {
+            return RSACheckV1(parameters, publicKey, charset, signType, keyFromFile);
+        }
+
+        /// <summary>
+        /// 异步通知参数验签,支持RSA、RSA2、SM2三种算法,推荐替换为相同功能的方法VerifyV1
+        /// V1版本方法将删除sign_type参数再进行验签,V2版本方法则不会
+        /// </summary>
+        /// <param name="parameters">待验签字符串</param>
+        /// <param name="publicKey">支付宝公钥</param>
+        /// <param name="charset">参数编码字符集</param>
+        /// <param name="signType">签名类型,RSA2或RSA、SM2</param>
+        /// <param name="keyFromFile">是否从文件加载支付宝公钥内容。
+        /// 如果该参数为true,则publicKey为公钥文件路径;
+        /// 如果该参数为false,则publicKey为公钥内容
+        /// </param>
+        /// <returns>true:验证成功;false:验证失败</returns>
+        public static bool RSACheckV1(IDictionary<string, string> parameters, string publicKey, string charset, string signType, bool keyFromFile)
+        {
+            ArgumentValidator.CheckNotNull(parameters, "parameters参数不可为Null");
+
+            string sign = parameters["sign"];
+            parameters.Remove("sign");
+            parameters.Remove("sign_type");
+            string signContent = GetSignContent(parameters);
+            return RSACheckContent(signContent, sign, publicKey, charset, signType, keyFromFile);
+        }
+
+        /// <summary>
+        /// 异步通知参数验签,支持RSA、RSA2、SM2三种算法
+        /// V1版本方法将删除sign_type参数再进行验签,V2版本方法则不会
+        /// </summary>
+        /// <param name="parameters">待验签的参数集合</param>
+        /// <param name="alipayPublicCertPath">支付宝公钥证书路径</param>
+        /// <param name="charset">参数编码字符集</param>
+        /// <param name="signType">签名类型,RSA2或RSA、SM2</param>
+        /// <returns>true:验证成功;false:验证失败</returns>
+        public static bool CertVerifyV2(IDictionary<string, string> parameters, string alipayPublicCertPath, string charset, string signType)
+        {
+            return RSACertCheckV2(parameters, alipayPublicCertPath, charset, signType);
+        }
+
+        /// <summary>
+        /// 异步通知参数验签,支持RSA、RSA2、SM2三种算法,推荐替换为相同功能的方法CertVerifyV2
+        /// V1版本方法将删除sign_type参数再进行验签,V2版本方法则不会
+        /// </summary>
+        /// <param name="parameters">待验签的参数集合</param>
+        /// <param name="alipayPublicCertPath">支付宝公钥证书路径</param>
+        /// <param name="charset">参数编码字符集</param>
+        /// <param name="signType">签名类型,RSA2或RSA、SM2</param>
+        /// <returns>true:验证成功;false:验证失败</returns>
+        public static bool RSACertCheckV2(IDictionary<string, string> parameters, string alipayPublicCertPath, string charset, string signType)
+        {
+            ArgumentValidator.CheckNotNull(parameters, "parameters参数不可为Null");
+
+            string alipayPublicKey = ExtractPemPublicKeyFromCert(alipayPublicCertPath);
+            return RSACheckV2(parameters, alipayPublicKey, charset, signType, false);
+        }
+
+        /// <summary>
+        /// 异步通知参数验签,支持RSA、RSA2、SM2三种算法
+        /// V1版本方法将删除sign_type参数再进行验签,V2版本方法则不会
+        /// </summary>
+        /// <param name="parameters">待验签字符串</param>
+        /// <param name="publicKey">支付宝公钥</param>
+        /// <param name="charset">参数编码字符集</param>
+        /// <param name="signType">签名类型,RSA2或RSA、SM2</param>
+        /// <param name="keyFromFile">是否从文件加载支付宝公钥内容。
+        /// 如果该参数为true,则publicKey为公钥文件路径;
+        /// 如果该参数为false,则publicKey为公钥内容
+        /// </param>
+        /// <returns>true:验证成功;false:验证失败</returns>
+        public static bool VerifyV2(IDictionary<string, string> parameters, string publicKey, string charset, string signType, bool keyFromFile)
+        {
+            return RSACheckV2(parameters, publicKey, charset, signType, keyFromFile);
+        }
+
+        /// <summary>
+        /// 异步通知参数验签,支持RSA、RSA2、SM2三种算法,推荐替换为相同功能的方法VerifyV2
+        /// V1版本方法将删除sign_type参数再进行验签,V2版本方法则不会
+        /// </summary>
+        /// <param name="parameters">待验签字符串</param>
+        /// <param name="publicKey">支付宝公钥</param>
+        /// <param name="charset">参数编码字符集</param>
+        /// <param name="signType">签名类型,RSA2或RSA、SM2</param>
+        /// <param name="keyFromFile">是否从文件加载支付宝公钥内容。
+        /// 如果该参数为true,则publicKey为公钥文件路径;
+        /// 如果该参数为false,则publicKey为公钥内容
+        /// </param>
+        /// <returns>true:验证成功;false:验证失败</returns>
+        public static bool RSACheckV2(IDictionary<string, string> parameters, string publicKey, string charset, string signType, bool keyFromFile)
+        {
+            ArgumentValidator.CheckNotNull(parameters, "parameters参数不可为Null");
+
+            string sign = parameters["sign"];
+            parameters.Remove("sign");
+            string signContent = GetSignContent(parameters);
+            return RSACheckContent(signContent, sign, publicKey, charset, signType, keyFromFile);
+        }
+
+        /// <summary>
+        /// 验证指定内容的签名,支持RSA、RSA2、SM2三种算法
+        /// </summary>
+        /// <param name="content">待验签的内容</param>
+        /// <param name="sign">签名字符串</param>
+        /// <param name="publicKey">支付宝公钥</param>
+        /// <param name="charset">字符集编码</param>
+        /// <param name="signType">签名算法类型,RSA2或RSA、SM2</param>
+        /// <param name="keyFromFile">是否从文件加载支付宝公钥内容。
+        /// 如果该参数为true,则publicKey为公钥文件路径;
+        /// 如果该参数为false,则publicKey为公钥内容
+        /// </param>
+        /// <returns>true:验证成功;false:验证失败</returns>
+        public static bool VerifyContent(string content, string sign, string publicKey, string charset, string signType, bool keyFromFile)
+        {
+            return RSACheckContent(content, sign, publicKey, charset, signType, keyFromFile);
+        }
+
+        /// <summary>
+        /// 验证指定内容的签名,支持RSA、RSA2、SM2三种算法,推荐替换为相同功能的方法VerifyContent
+        /// </summary>
+        /// <param name="content">待验签的内容</param>
+        /// <param name="sign">签名字符串</param>
+        /// <param name="publicKey">支付宝公钥</param>
+        /// <param name="charset">字符集编码</param>
+        /// <param name="signType">签名算法类型,RSA2或RSA、SM2</param>
+        /// <param name="keyFromFile">是否从文件加载支付宝公钥内容。
+        /// 如果该参数为true,则publicKey为公钥文件路径;
+        /// 如果该参数为false,则publicKey为公钥内容
+        /// </param>
+        /// <returns>true:验证成功;false:验证失败</returns>
+        public static bool RSACheckContent(string content, string sign, string publicKey, string charset, string signType, bool keyFromFile)
+        {
+            if (keyFromFile)
+            {
+                ArgumentValidator.CheckArgument(!String.IsNullOrEmpty(publicKey), "公钥文件路径不可为空。");
+                publicKey = File.ReadAllText(publicKey);
+            }
+
+            return AsymmetricManager.GetByName(signType).Verify(content, charset, publicKey, sign);
+        }
+
+        /// <summary>
+        /// 对指定参数进行签名,支持RSA、RSA2、SM2三种算法
+        /// </summary>
+        /// <param name="parameters">参数集合</param>
+        /// <param name="privateKey">商户私钥</param>
+        /// <param name="charset">字符集编码</param>
+        /// <param name="signType">签名算法类型,RSA2或RSA、SM2</param>
+        /// <param name="keyFromFile">是否从私钥证书文件中加载私钥
+        /// 如果该参数为true,privateKey为私钥证书文件路径;
+        /// 如果该参数为false,privateKey为私钥内容字符串
+        /// </param>
+        /// <returns>签名字符串</returns>
+        public static string Sign(IDictionary<string, string> parameters, string privateKey, string charset, string signType, bool keyFromFile)
+        {
+            return RSASign(parameters, privateKey, charset, signType, keyFromFile);
+        }
+
+        /// <summary>
+        /// 对指定参数进行签名,支持RSA、RSA2、SM2三种算法,推荐替换为相同功能的方法Sign
+        /// </summary>
+        /// <param name="parameters">参数集合</param>
+        /// <param name="privateKey">商户私钥</param>
+        /// <param name="charset">字符集编码</param>
+        /// <param name="signType">签名算法类型,RSA2或RSA、SM2</param>
+        /// <param name="keyFromFile">是否从私钥证书文件中加载私钥
+        /// 如果该参数为true,privateKey为私钥证书文件路径;
+        /// 如果该参数为false,privateKey为私钥内容字符串
+        /// </param>
+        /// <returns>签名字符串</returns>
+        public static string RSASign(IDictionary<string, string> parameters, string privateKey, string charset, string signType, bool keyFromFile)
+        {
+            ArgumentValidator.CheckNotNull(parameters, "parameters参数不可为Null");
+
+            string signContent = GetSignContent(parameters);
+            return RSASignCharSet(signContent, privateKey, charset, signType, keyFromFile);
+        }
+
+        /// <summary>
+        /// 对指定内容进行签名,支持RSA、RSA2、SM2三种算法
+        /// </summary>
+        /// <param name="data">内容字符串</param>
+        /// <param name="privateKey">商户私钥</param>
+        /// <param name="charset">字符集编码</param>
+        /// <param name="signType">签名算法类型,RSA2或RSA、SM2</param>
+        /// <param name="keyFromFile">是否从私钥证书文件中加载私钥
+        /// 如果该参数为true,privateKey为私钥证书文件路径;
+        /// 如果该参数为false,privateKey为私钥内容字符串
+        /// </param>
+        /// <returns>签名字符串</returns>
+        public static string Sign(string data, string privateKey, string charset, string signType, bool keyFromFile)
+        {
+            return RSASign(data, privateKey, charset, signType, keyFromFile);
+        }
+
+        /// <summary>
+        /// 对指定内容进行签名,支持RSA、RSA2、SM2三种算法,推荐替换为相同功能的方法Sign
+        /// </summary>
+        /// <param name="data">内容字符串</param>
+        /// <param name="privateKey">商户私钥</param>
+        /// <param name="charset">字符集编码</param>
+        /// <param name="signType">签名算法类型,RSA2或RSA、SM2</param>
+        /// <param name="keyFromFile">是否从私钥证书文件中加载私钥
+        /// 如果该参数为true,privateKey为私钥证书文件路径;
+        /// 如果该参数为false,privateKey为私钥内容字符串
+        /// </param>
+        /// <returns>签名字符串</returns>
+        public static string RSASign(string data, string privateKey, string charset, string signType, bool keyFromFile)
+        {
+            return RSASignCharSet(data, privateKey, charset, signType, keyFromFile);
+        }
+
+        /// <summary>
+        /// 验签并解密,目前仅适用于生活号
+        /// </summary>
+        /// <param name="parameters">待验签并解密的参数</param>
+        /// <param name="alipayPublicKey">支付宝公钥字符串,用于验签</param>
+        /// <param name="cusPrivateKey">商户私钥字符串,用于解密</param>
+        /// <param name="isCheckSign">是否检查签名</param>
+        /// <param name="isDecrypt">是否解密</param>
+        /// <param name="signType">非对称加密算法类型,RSA2或RSA、SM2</param>
+        /// <param name="keyFromFile">是否从文件加载支付宝公钥和商户私钥
+        /// 如果该参数为true,alipayPublicKey为公钥文件路径,cusPrivateKey为私钥证书文件路径;
+        /// 如果该参数为false,则publicKeyPem为公钥内容字符串,cusPrivateKey为私钥内容字符串
+        /// </param>
+        /// <returns>验签解密后的内容</returns>
+        public static string CheckSignAndDecrypt(IDictionary<string, string> parameters, string alipayPublicKey, string cusPrivateKey,
+            bool isCheckSign, bool isDecrypt, string signType, bool keyFromFile)
+        {
+            ArgumentValidator.CheckNotNull(parameters, "parameters参数不可为Null");
+
+            string charset = parameters["charset"];
+            string bizContent = parameters["biz_content"];
+            if (isCheckSign)
+            {
+                if (!RSACheckV2(parameters, alipayPublicKey, charset, signType, keyFromFile))
+                {
+                    throw new Exception("rsaCheck failure:rsaParams=" + parameters);
+                }
+            }
+
+            if (isDecrypt)
+            {
+                return RSADecrypt(bizContent, cusPrivateKey, charset, signType, keyFromFile);
+            }
+
+            return bizContent;
+        }
+
+        /// <summary>
+        /// 加密并加签,目前仅适用于生活号
+        /// </summary>
+        /// <param name="bizContent">待加密和加签的原文</param>
+        /// <param name="alipayPublicKey">支付宝公钥字符串,用于加密</param>
+        /// <param name="cusPrivateKey">商户私钥字符串,用于加签</param>
+        /// <param name="charset">字符集编码</param>
+        /// <param name="isEncrypt">是否需要加密</param>
+        /// <param name="isSign">是否需要加签</param>
+        /// <param name="signType">非对称加密算法类型,RSA或RSA2、SM2</param>
+        /// <param name="keyFromFile">是否从文件加载支付宝公钥和商户私钥
+        /// 如果该参数为true,alipayPublicKey为公钥文件路径,cusPrivateKey为私钥证书文件路径;
+        /// 如果该参数为false,则publicKeyPem为公钥内容字符串,cusPrivateKey为私钥内容字符串
+        /// </param>
+        /// <returns>加密加签后的内容</returns>
+        public static string EncryptAndSign(string bizContent, string alipayPublicKey, string cusPrivateKey, string charset,
+            bool isEncrypt, bool isSign, string signType, bool keyFromFile)
+        {
+            StringBuilder sb = new StringBuilder();
+            if (string.IsNullOrEmpty(charset))
+            {
+                charset = DEFAULT_CHARSET;
+            }
+            sb.Append("<?xml version=\"1.0\" encoding=\"" + charset + "\"?>");
+            if (isEncrypt)
+            {
+                // 加密
+                sb.Append("<alipay>");
+                String encrypted = RSAEncrypt(bizContent, alipayPublicKey, charset, keyFromFile);
+                sb.Append("<response>" + encrypted + "</response>");
+                sb.Append("<encryption_type>" + signType + "</encryption_type>");
+                if (isSign)
+                {
+                    String sign = RSASign(encrypted, cusPrivateKey, charset, signType, keyFromFile);
+                    sb.Append("<sign>" + sign + "</sign>");
+                    sb.Append("<sign_type>" + signType + "</sign_type>");
+                }
+                sb.Append("</alipay>");
+            }
+            else if (isSign)
+            {
+                // 不加密,但需要签名
+                sb.Append("<alipay>");
+                sb.Append("<response>" + bizContent + "</response>");
+                String sign = RSASign(bizContent, cusPrivateKey, charset, signType, keyFromFile);
+                sb.Append("<sign>" + sign + "</sign>");
+                sb.Append("<sign_type>" + signType + "</sign_type>");
+                sb.Append("</alipay>");
+            }
+            else
+            {
+                // 不加密,不加签
+                sb.Append(bizContent);
+            }
+            return sb.ToString();
+        }
+
+
+
+
+
+        public static string GetSignContent(IDictionary<string, string> parameters)
+        {
+            // 第一步:把字典按Key的字母顺序排序
+            IDictionary<string, string> sortedParams = new SortedDictionary<string, string>(parameters, StringComparer.Ordinal);
+            IEnumerator<KeyValuePair<string, string>> dem = sortedParams.GetEnumerator();
+
+            // 第二步:把所有参数名和参数值串在一起
+            StringBuilder query = new StringBuilder("");
+            while (dem.MoveNext())
+            {
+                string key = dem.Current.Key;
+                string value = dem.Current.Value;
+                if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
+                {
+                    query.Append(key).Append("=").Append(value).Append("&");
+                }
+            }
+            string content = query.ToString().Substring(0, query.Length - 1);
+
+            return content;
+        }
+
+        public static string RSASignCharSet(string data, string privateKeyPem, string charset, string signType, bool keyFromFile)
+        {
+            if (keyFromFile)
+            {
+                ArgumentValidator.CheckArgument(!String.IsNullOrEmpty(privateKeyPem), "私钥文件路径不可为空。");
+                privateKeyPem = LoadPrivateKeyFromRSACertFile(privateKeyPem);
+            }
+
+            return AsymmetricManager.GetByName(signType).Sign(data, charset, privateKeyPem);
+        }
+
+        public static string RSAEncrypt(string content, string publicKeyPem, string charset, bool keyFromFile)
+        {
+            if (keyFromFile)
+            {
+                ArgumentValidator.CheckArgument(!String.IsNullOrEmpty(publicKeyPem), "公钥文件路径不可为空。");
+                publicKeyPem = File.ReadAllText(publicKeyPem);
+            }
+
+            return AsymmetricManager.GetByName("RSA").Encrypt(content, charset, publicKeyPem);
+        }
+
+        public static string RSADecrypt(string content, string privateKeyPem, string charset, string signType, bool keyFromFile)
+        {
+            if (keyFromFile)
+            {
+                ArgumentValidator.CheckArgument(!String.IsNullOrEmpty(privateKeyPem), "私钥文件路径不可为空。");
+                privateKeyPem = LoadPrivateKeyFromRSACertFile(privateKeyPem);
+            }
+
+            return AsymmetricManager.GetByName(signType).Decrypt(content, charset, privateKeyPem);
+        }
+
+        private static string LoadPrivateKeyFromRSACertFile(string filename)
+        {
+            using (FileStream fs = File.OpenRead(filename))
+            {
+                byte[] data = new byte[fs.Length];
+                fs.Read(data, 0, data.Length);
+                if (data[0] != 0x30)
+                {
+                    return GetPem("RSA PRIVATE KEY", data);
+                }
+                throw new Exception("证书文件格式不符合预期,无法提取私钥。");
+            }
+        }
+
+        private static string GetPem(string type, byte[] data)
+        {
+            string pem = Encoding.UTF8.GetString(data);
+            string header = String.Format("-----BEGIN {0}-----\\n", type);
+            string footer = String.Format("-----END {0}-----", type);
+            int start = pem.IndexOf(header, StringComparison.Ordinal) + header.Length;
+            int end = pem.IndexOf(footer, start, StringComparison.Ordinal);
+
+            return pem.Substring(start, (end - start));
+        }
+
+        public static SignSourceData ExtractSignContent(String str, int begin)
+        {
+            if (str == null)
+            {
+                return null;
+            }
+
+            int beginIndex = ExtractBeginPosition(str, begin);
+            if (beginIndex >= str.Length)
+            {
+                return null;
+            }
+
+            int endIndex = ExtractEndPosition(str, beginIndex);
+            return new SignSourceData()
+            {
+                SourceData = str.Substring(beginIndex, endIndex - beginIndex),
+                BeginIndex = beginIndex,
+                EndIndex = endIndex
+            };
+        }
+
+        private static int ExtractBeginPosition(String responseString, int begin)
+        {
+            int beginPosition = begin;
+            //找到第一个左大括号(对应响应的是JSON对象的情况:普通调用OpenAPI响应明文)
+            //或者双引号(对应响应的是JSON字符串的情况:加密调用OpenAPI响应Base64串),作为待验签内容的起点
+            while (beginPosition < responseString.Length
+                    && responseString[beginPosition] != '{'
+                    && responseString[beginPosition] != '"')
+            {
+                ++beginPosition;
+            }
+            return beginPosition;
+        }
+
+        private static int ExtractEndPosition(String responseString, int beginPosition)
+        {
+            //提取明文验签内容终点
+            if (responseString[beginPosition] == '{')
+            {
+                return ExtractJsonObjectEndPosition(responseString, beginPosition);
+            }
+            //提取密文验签内容终点
+            else
+            {
+                return ExtractJsonBase64ValueEndPosition(responseString, beginPosition);
+            }
+        }
+
+        private static int ExtractJsonBase64ValueEndPosition(String responseString, int beginPosition)
+        {
+            for (int index = beginPosition; index < responseString.Length; ++index)
+            {
+                //找到第2个双引号作为终点,由于中间全部是Base64编码的密文,所以不会有干扰的特殊字符
+                if (responseString[index] == '"' && index != beginPosition)
+                {
+                    return index + 1;
+                }
+            }
+            //如果没有找到第2个双引号,说明验签内容片段提取失败,直接尝试选取剩余整个响应字符串进行验签
+            return responseString.Length;
+        }
+
+        private static int ExtractJsonObjectEndPosition(String responseString, int beginPosition)
+        {
+            //记录当前尚未发现配对闭合的大括号
+            LinkedList<String> braces = new LinkedList<String>();
+            //记录当前字符是否在双引号中
+            bool inQuotes = false;
+            //记录当前字符前面连续的转义字符个数
+            int consecutiveEscapeCount = 0;
+            //从待验签字符的起点开始遍历后续字符串,找出待验签字符串的终止点,终点即是与起点{配对的}
+            for (int index = beginPosition; index < responseString.Length; ++index)
+            {
+                //提取当前字符
+                char currentChar = responseString[index];
+
+                //如果当前字符是"且前面有偶数个转义标记(0也是偶数)
+                if (currentChar == '"' && consecutiveEscapeCount % 2 == 0)
+                {
+                    //是否在引号中的状态取反
+                    inQuotes = !inQuotes;
+                }
+                //如果当前字符是{且不在引号中
+                else if (currentChar == '{' && !inQuotes)
+                {
+                    //将该{加入未闭合括号中
+                    braces.AddLast("{");
+                }
+                //如果当前字符是}且不在引号中
+                else if (currentChar == '}' && !inQuotes)
+                {
+                    //弹出一个未闭合括号
+                    braces.RemoveLast();
+                    //如果弹出后,未闭合括号为空,说明已经找到终点
+                    if (braces.Count == 0)
+                    {
+                        return index + 1;
+                    }
+                }
+
+                //如果当前字符是转义字符
+                if (currentChar == '\\')
+                {
+                    //连续转义字符个数+1
+                    ++consecutiveEscapeCount;
+                }
+                else
+                {
+                    //连续转义字符个数置0
+                    consecutiveEscapeCount = 0;
+                }
+            }
+
+            //如果没有找到配对的闭合括号,说明验签内容片段提取失败,直接尝试选取剩余整个响应字符串进行验签
+            return responseString.Length;
+        }
+
+        /// <summary>
+        /// 获取公钥证书序列号
+        /// </summary>
+        /// <param name="certPath">公钥证书路径</param>
+        /// <returns>公钥证书序列号</returns>
+        public static String GetCertSN(String certPath)
+        {
+            X509Certificate cert = AntCertificationUtil.ParseCert(File.ReadAllText(certPath));
+            return AntCertificationUtil.GetCertSN(cert);
+        }
+
+
+        [Obsolete("因之前方法命名不符合C#规范而废弃的方法,请替换为EncryptAndSign方法")]
+        public static string encryptAndSign(string bizContent, string alipayPublicKey, string cusPrivateKey, string charset,
+           bool isEncrypt, bool isSign, string signType, bool keyFromFile)
+        {
+            return EncryptAndSign(bizContent, alipayPublicKey, cusPrivateKey, charset, isEncrypt, isSign, signType, keyFromFile);
+        }
+
+        [Obsolete("请替换为EncryptAndSign方法,显式设置参数:signTyp=RSA,keyFromFile=true")]
+        public static string encryptAndSign(string bizContent, string alipayPublicKey, string cusPrivateKey, string charset,
+            bool isEncrypt, bool isSign)
+        {
+            return EncryptAndSign(bizContent, alipayPublicKey, cusPrivateKey, charset, isEncrypt, isSign, "RSA", true);
+        }
+
+        [Obsolete("请替换为未废弃的有完整参数列表的重载版本,明确指定各参数的值")]
+        public static string CheckSignAndDecrypt(IDictionary<string, string> parameters, string alipayPublicKey,
+                                             string cusPrivateKey, bool isCheckSign, bool isDecrypt)
+        {
+            return CheckSignAndDecrypt(parameters, alipayPublicKey, cusPrivateKey, isCheckSign, isDecrypt, "RSA", true);
+        }
+
+        [Obsolete("请替换为未废弃的有完整参数列表的重载版本,明确指定各参数的值")]
+        public static string RSASign(IDictionary<string, string> parameters, string privateKeyPem, string charset, string signType)
+        {
+            string signContent = GetSignContent(parameters);
+
+            return RSASignCharSet(signContent, privateKeyPem, charset, signType);
+        }
+
+        [Obsolete("请替换为未废弃的有完整参数列表的重载版本,明确指定各参数的值")]
+        public static string RSASign(string data, string privateKeyPem, string charset, string signType)
+        {
+            return RSASignCharSet(data, privateKeyPem, charset, signType);
+        }
+
+        [Obsolete("请替换为未废弃的有完整参数列表的重载版本,按照惯例将keyFromFile参数列在最后")]
+        public static string RSASign(IDictionary<string, string> parameters, string privateKey, string charset, bool keyFromFile, string signType)
+        {
+            string signContent = GetSignContent(parameters);
+            return RSASignCharSet(signContent, privateKey, charset, signType, keyFromFile);
+        }
+
+        [Obsolete("请替换为未废弃的有完整参数列表的重载版本,明确指定各参数的值")]
+        public static string RSASignCharSet(string data, string privateKeyPem, string charset, string signType)
+        {
+            return RSASignCharSet(data, privateKeyPem, charset, signType, true);
+        }
+
+        [Obsolete("请替换为未废弃的有完整参数列表的重载版本,明确指定各参数的值")]
+        public static bool RSACheckV1(IDictionary<string, string> parameters, string publicKeyPem, string charset)
+        {
+            string sign = parameters["sign"];
+
+            parameters.Remove("sign");
+            parameters.Remove("sign_type");
+            string signContent = GetSignContent(parameters);
+            return RSACheckContent(signContent, sign, publicKeyPem, charset, "RSA");
+        }
+
+        [Obsolete("请替换为未废弃的有完整参数列表的重载版本,明确指定各参数的值")]
+        public static bool RSACheckV1(IDictionary<string, string> parameters, string publicKeyPem)
+        {
+            string sign = parameters["sign"];
+
+            parameters.Remove("sign");
+            parameters.Remove("sign_type");
+            string signContent = GetSignContent(parameters);
+
+            return RSACheckContent(signContent, sign, publicKeyPem, DEFAULT_CHARSET, "RSA");
+        }
+
+        [Obsolete("请替换为未废弃的有完整参数列表的重载版本,明确指定各参数的值")]
+        public static bool RSACheckV2(IDictionary<string, string> parameters, string publicKeyPem)
+        {
+            string sign = parameters["sign"];
+
+            parameters.Remove("sign");
+            string signContent = GetSignContent(parameters);
+
+            return RSACheckContent(signContent, sign, publicKeyPem, DEFAULT_CHARSET, "RSA");
+        }
+
+        [Obsolete("请替换为未废弃的有完整参数列表的重载版本,明确指定各参数的值")]
+        public static bool RSACheckV2(IDictionary<string, string> parameters, string publicKeyPem, string charset)
+        {
+            string sign = parameters["sign"];
+
+            parameters.Remove("sign");
+            string signContent = GetSignContent(parameters);
+
+            return RSACheckContent(signContent, sign, publicKeyPem, charset, "RSA");
+        }
+
+        [Obsolete("请替换为未废弃的有完整参数列表的重载版本,明确指定各参数的值")]
+        public static bool RSACheckContent(string signContent, string sign, string publicKeyPem, string charset, string signType)
+        {
+            return RSACheckContent(signContent, sign, publicKeyPem, charset, signType, true);
+        }
+
+        [Obsolete("请替换为未废弃的有完整参数列表的重载版本,明确指定各参数的值")]
+        public static bool RSACheckContent(string signContent, string sign, string publicKeyPem, string charset, bool keyFromFile)
+        {
+            return RSACheckContent(signContent, sign, publicKeyPem, charset, "RSA", keyFromFile);
+        }
+
+        [Obsolete("请替换为未废弃的有完整参数列表的重载版本,明确指定各参数的值")]
+        public static string RSAEncrypt(string content, string publicKeyPem, string charset)
+        {
+            return RSAEncrypt(content, publicKeyPem, charset, true);
+        }
+
+        [Obsolete("请替换为未废弃的有完整参数列表的重载版本,明确指定各参数的值")]
+        public static string RSADecrypt(string content, string privateKeyPem, string charset, string signType)
+        {
+            return RSADecrypt(content, privateKeyPem, charset, signType, true);
+        }
+    }
+}

+ 326 - 0
PaySharp.Alipay/Util/AntCertificationUtil.cs

@@ -0,0 +1,326 @@
+using System.Collections.Generic;
+using System;
+using Org.BouncyCastle.X509;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+using System.Security.Cryptography;
+using System.Text;
+using System.Linq;
+
+namespace PaySharp.Alipay.Util
+{
+    /// <summary>
+    /// 证书相关工具类
+    /// </summary>
+    public static class AntCertificationUtil
+    {
+        /// <summary>
+        /// 提取根证书序列号
+        /// </summary>
+        /// <param name="rootCertContent">根证书文本</param>
+        /// <returns>根证书序列号</returns>
+        public static string GetRootCertSN(string rootCertContent)
+        {
+            string rootCertSN = "";
+            try
+            {
+                List<X509Certificate> x509Certificates = ReadPemCertChain(rootCertContent);
+                foreach (X509Certificate cert in x509Certificates)
+                {
+                    //只提取与指定算法类型匹配的证书的序列号
+                    if (cert.SigAlgOid.StartsWith("1.2.840.113549.1.1", StringComparison.Ordinal))
+                    {
+                        string certSN = GetCertSN(cert);
+                        if (string.IsNullOrEmpty(rootCertSN))
+                        {
+                            rootCertSN = certSN;
+                        }
+                        else
+                        {
+                            rootCertSN = rootCertSN + "_" + certSN;
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                throw new Exception("提取根证书序列号失败。" + ex.Message);
+            }
+            return rootCertSN;
+        }
+
+        /// <summary>
+        /// 反序列化证书文本
+        /// </summary>
+        /// <param name="certContent">证书文本</param>
+        /// <returns>X509Certificate证书对象</returns>
+        public static X509Certificate ParseCert(string certContent)
+        {
+            return new X509CertificateParser().ReadCertificate(Encoding.UTF8.GetBytes(certContent));
+        }
+
+        /// <summary>
+        /// 计算指定证书的序列号
+        /// </summary>
+        /// <param name="cert">证书</param>
+        /// <returns>序列号</returns>
+        public static string GetCertSN(X509Certificate cert)
+        {
+            string issuerDN = cert.IssuerDN.ToString();
+            //提取出的证书的issuerDN本身是以CN开头的,则无需逆序,直接返回
+            if (issuerDN.StartsWith("CN", StringComparison.Ordinal))
+            {
+                return CalculateMd5(issuerDN + cert.SerialNumber);
+            }
+            List<string> attributes = issuerDN.Split(',').ToList();
+            attributes.Reverse();
+            return CalculateMd5(string.Join(",", attributes.ToArray()) + cert.SerialNumber);
+        }
+
+        /// <summary>
+        /// 校验证书链是否可信
+        /// </summary>
+        /// <param name="certContent">需要验证的目标证书或者证书链文本</param>
+        /// <param name="rootCertContent">可信根证书列表文本</param>
+        /// <returns>true:证书可信;false:证书不可信</returns>
+        public static bool IsTrusted(string certContent, string rootCertContent)
+        {
+            List<X509Certificate> certs = ReadPemCertChain(certContent);
+            List<X509Certificate> rootCerts = ReadPemCertChain(rootCertContent);
+            return VerifyCertChain(certs, rootCerts);
+        }
+
+        /// <summary>
+        /// 从证书链文本反序列化证书链集合
+        /// </summary>
+        /// <param name="cert">证书链文本</param>
+        /// <returns>证书链集合</returns>
+        private static List<X509Certificate> ReadPemCertChain(string cert)
+        {
+            System.Collections.ICollection collection = new X509CertificateParser().ReadCertificates(Encoding.UTF8.GetBytes(cert));
+            List<X509Certificate> result = new List<X509Certificate>();
+            foreach (var each in collection)
+            {
+                result.Add((X509Certificate)each);
+            }
+            return result;
+        }
+
+        /// <summary>
+        /// 将证书链按照完整的签发顺序进行排序,排序后证书链为:[issuerA, subjectA]-[issuerA, subjectB]-[issuerB, subjectC]-[issuerC, subjectD]...
+        /// </summary>
+        /// <param name="certChain">未排序的证书链</param>
+        /// <returns>true:排序成功;false:证书链不完整</returns>
+        private static bool SortCertChain(List<X509Certificate> certChain)
+        {
+            //主题和证书的映射
+            Dictionary<X509Name, X509Certificate> subject2CertMap = new Dictionary<X509Name, X509Certificate>();
+            //签发者和证书的映射
+            Dictionary<X509Name, X509Certificate> issuer2CertMap = new Dictionary<X509Name, X509Certificate>();
+            //是否包含自签名证书
+            bool hasSelfSignedCert = false;
+            foreach (X509Certificate cert in certChain)
+            {
+                if (IsSelfSigned(cert))
+                {
+                    if (hasSelfSignedCert)
+                    {
+                        //同一条证书链中只能有一个自签名证书
+                        return false;
+                    }
+                    hasSelfSignedCert = true;
+                }
+                subject2CertMap[cert.SubjectDN] = cert;
+                issuer2CertMap[cert.IssuerDN] = cert;
+            }
+
+            List<X509Certificate> orderedCertChain = new List<X509Certificate>();
+
+            X509Certificate current = certChain[0];
+
+            AddressingUp(subject2CertMap, orderedCertChain, current);
+            AddressingDown(issuer2CertMap, orderedCertChain, current);
+
+            //说明证书链不完整
+            if (certChain.Count != orderedCertChain.Count)
+            {
+                return false;
+            }
+
+            //用排序后的结果覆盖传入的证书链集合
+            for (int i = 0; i < orderedCertChain.Count; i++)
+            {
+                certChain[i] = orderedCertChain[i];
+            }
+            return true;
+        }
+
+        private static bool IsSelfSigned(X509Certificate cert)
+        {
+            return cert.SubjectDN.Equivalent(cert.IssuerDN);
+        }
+
+        /// <summary>
+        /// 向上构造证书链
+        /// </summary>
+        /// <param name="subject2CertMap">主题与证书的映射</param>
+        /// <param name="orderedCertChain">储存排序后的证书链集合</param>
+        /// <param name="current">当前需要插入排序后的证书链集合中的证书</param>
+        private static void AddressingUp(Dictionary<X509Name, X509Certificate> subject2CertMap,
+            List<X509Certificate> orderedCertChain, X509Certificate current)
+        {
+            orderedCertChain.Insert(0, current);
+            if (IsSelfSigned(current))
+            {
+                return;
+            }
+
+            if (!subject2CertMap.ContainsKey(current.IssuerDN))
+            {
+                return;
+            }
+
+            X509Certificate issuer = subject2CertMap[current.IssuerDN];
+            AddressingUp(subject2CertMap, orderedCertChain, issuer);
+        }
+
+        /// <summary>
+        /// 向下构造证书链
+        /// </summary>
+        /// <param name="issuer2CertMap">签发者和证书的映射</param>
+        /// <param name="certChain">储存排序后的证书链集合</param>
+        /// <param name="current">当前需要插入排序后的证书链集合中的证书</param>
+        private static void AddressingDown(Dictionary<X509Name, X509Certificate> issuer2CertMap,
+            List<X509Certificate> certChain, X509Certificate current)
+        {
+            if (!issuer2CertMap.ContainsKey(current.SubjectDN))
+            {
+                return;
+            }
+
+            X509Certificate subject = issuer2CertMap[current.SubjectDN];
+            if (IsSelfSigned(subject))
+            {
+                return;
+            }
+            certChain.Add(subject);
+            AddressingDown(issuer2CertMap, certChain, subject);
+        }
+
+        /// <summary>
+        /// 验证证书是否是信任证书库中的证书签发的
+        /// </summary>
+        /// <param name="cert">待验证证书</param>
+        /// <param name="rootCerts">可信根证书列表</param>
+        /// <returns>true:验证通过;false:验证不通过</returns>
+        private static bool VerifyCert(X509Certificate cert, List<X509Certificate> rootCerts)
+        {
+            if (!cert.IsValidNow)
+            {
+                return false;
+            }
+
+            Dictionary<X509Name, X509Certificate> subject2CertMap = new Dictionary<X509Name, X509Certificate>();
+            foreach (X509Certificate root in rootCerts)
+            {
+                subject2CertMap[root.SubjectDN] = root;
+            }
+
+            X509Name issuerDN = cert.IssuerDN;
+            if (!subject2CertMap.ContainsKey(issuerDN))
+            {
+                return false;
+            }
+
+            X509Certificate issuer = subject2CertMap[issuerDN];
+            try
+            {
+                AsymmetricKeyParameter publicKey = issuer.GetPublicKey();
+                cert.Verify(publicKey);
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine("证书验证出现异常。" + ex.Message);
+                return false;
+            }
+            return true;
+        }
+
+        /// <summary>
+        /// 验证证书列表
+        /// </summary>
+        /// <param name="certs">待验证的证书列表</param>
+        /// <param name="rootCerts">可信根证书列表</param>
+        /// <returns>true:验证通过;false:验证不通过</returns>
+        private static bool VerifyCertChain(List<X509Certificate> certs, List<X509Certificate> rootCerts)
+        {
+            //证书列表排序,形成排序后的证书链
+            bool sorted = SortCertChain(certs);
+            if (!sorted)
+            {
+                //不是完整的证书链
+                return false;
+            }
+
+            //先验证第一个证书是不是信任库中证书签发的
+            X509Certificate previous = certs[0];
+            bool firstOK = VerifyCert(previous, rootCerts);
+            if (!firstOK || certs.Count == 1)
+            {
+                return firstOK;
+            }
+
+            //验证证书链
+            for (int i = 1; i < certs.Count; i++)
+            {
+                try
+                {
+                    X509Certificate cert = certs[i];
+                    if (!cert.IsValidNow)
+                    {
+                        return false;
+                    }
+
+                    //用上级证书的公钥验证本证书是否是上级证书签发的
+                    cert.Verify(previous.GetPublicKey());
+
+                    previous = cert;
+                }
+                catch (Exception ex)
+                {
+                    //证书链验证失败
+                    Console.WriteLine("证书链验证失败。" + ex.Message);
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+
+        private static string CalculateMd5(string input)
+        {
+            using (MD5 md5 = new MD5CryptoServiceProvider())
+            {
+                string result = "";
+                byte[] bytes = md5.ComputeHash(Encoding.GetEncoding("utf-8").GetBytes(input));
+                for (int i = 0; i < bytes.Length; i++)
+                {
+                    result += bytes[i].ToString("x2");
+                }
+                return result;
+            }
+        }
+
+        /// <summary>
+        /// 从证书中提取公钥并转换为PEM编码
+        /// </summary>
+        /// <param name="input">证书</param>
+        /// <returns>PEM编码公钥</returns>
+        public static string ExtractPemPublicKeyFromCert(X509Certificate input)
+        {
+            SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(input.GetPublicKey());
+            return Convert.ToBase64String(subjectPublicKeyInfo.GetDerEncoded());
+        }
+    }
+}

+ 34 - 0
PaySharp.Alipay/Util/ArgumentValidator.cs

@@ -0,0 +1,34 @@
+using System;
+
+namespace PaySharp.Alipay.Util
+{
+    /// <summary>
+    /// 参数校验类
+    /// </summary>
+    public class ArgumentValidator
+    {
+        public static void CheckArgument(bool expression, string errorMessage)
+        {
+            if (!expression)
+            {
+                throw new Exception(errorMessage);
+            }
+        }
+
+        public static void CheckNotNull(object value, string errorMessage)
+        {
+            if (value == null)
+            {
+                throw new Exception(errorMessage);
+            }
+        }
+
+        public static void EnsureNull(object value, string errorMessage)
+        {
+            if (value != null)
+            {
+                throw new Exception(errorMessage);
+            }
+        }
+    }
+}

+ 33 - 0
PaySharp.Alipay/Util/Asymmetric/AsymmetricManager.cs

@@ -0,0 +1,33 @@
+using System;
+
+namespace PaySharp.Alipay.Util.Asymmetric
+{
+    /// <summary>
+    /// 非对称加密算法管理类
+    /// </summary>
+    public static class AsymmetricManager
+    {
+        /// <summary>
+        /// 根据算法名称(RSA、RSA2、SM2)实例化具体算法的加密器
+        /// </summary>
+        /// <param name="type">算法名称</param>
+        /// <returns>具体算法的加密器</returns>
+        public static IAsymmetricEncryptor GetByName(string type)
+        {
+            if ("RSA".Equals(type))
+            {
+                return new RSAEncryptor();
+            }
+            if ("RSA2".Equals(type))
+            {
+                return new RSA2Encryptor();
+            }
+            if ("SM2".Equals(type))
+            {
+                return new SM2Encryptor();
+            }
+
+            throw new Exception("无效的非对称加密类型:[" + type + "],可选值为:RSA、RSA2和SM2。");
+        }
+    }
+}

+ 114 - 0
PaySharp.Alipay/Util/Asymmetric/BaseAsymmetricEncryptor.cs

@@ -0,0 +1,114 @@
+using System;
+
+namespace PaySharp.Alipay.Util.Asymmetric
+{
+    /// <summary>
+    /// 非对称加密算法
+    /// </summary>
+    public abstract class BaseAsymmetricEncryptor : IAsymmetricEncryptor
+    {
+        /// <summary>
+        /// 默认字符集编码。现在都推荐使用UTF-8,之前默认是GBK,保持向下兼容性
+        /// </summary>
+        private static readonly string DEFAULT_CHARSET = "GBK";
+
+        public string Decrypt(string cipherTextBase64, string charset, string privateKey)
+        {
+            try
+            {
+                ArgumentValidator.CheckNotNull(cipherTextBase64, "密文不可为Null");
+                ArgumentValidator.CheckArgument(!String.IsNullOrEmpty(privateKey), "私钥不可为空");
+
+                if (String.IsNullOrEmpty(charset))
+                {
+                    charset = DEFAULT_CHARSET;
+                }
+                return DoDecrypt(cipherTextBase64, charset, privateKey);
+            }
+            catch (Exception ex)
+            {
+                String errorMessage = GetAsymmetricType() + "非对称解密遭遇异常,请检查私钥格式是否正确。" + ex.Message +
+                    " cipherTextBase64=" + cipherTextBase64 + ",charset=" + charset + ",privateKeySize=" + privateKey.Length;
+                Console.WriteLine(errorMessage);
+                throw new Exception(errorMessage, ex);
+            }
+        }
+
+        public string Encrypt(string plainText, string charset, string publicKey)
+        {
+            try
+            {
+                ArgumentValidator.CheckNotNull(plainText, "密文不可为Null");
+                ArgumentValidator.CheckArgument(!String.IsNullOrEmpty(publicKey), "公钥不可为空");
+
+                if (String.IsNullOrEmpty(charset))
+                {
+                    charset = DEFAULT_CHARSET;
+                }
+                return DoEncrypt(plainText, charset, publicKey);
+            }
+            catch (Exception ex)
+            {
+                String errorMessage = GetAsymmetricType() + "非对称解密遭遇异常,请检查公钥格式是否正确。" + ex.Message +
+                    " plainText=" + plainText + ",charset=" + charset + ",publicKey=" + publicKey;
+                Console.WriteLine(errorMessage);
+                throw new Exception(errorMessage, ex);
+            }
+        }
+
+        public string Sign(string content, string charset, string privateKey)
+        {
+            try
+            {
+                ArgumentValidator.CheckNotNull(content, "待签名内容不可为Null");
+                ArgumentValidator.CheckArgument(!String.IsNullOrEmpty(privateKey), "私钥不可为空");
+
+                if (String.IsNullOrEmpty(charset))
+                {
+                    charset = DEFAULT_CHARSET;
+                }
+                return DoSign(content, charset, privateKey);
+            }
+            catch (Exception ex)
+            {
+                String errorMessage = GetAsymmetricType() + "签名遭遇异常,请检查私钥格式是否正确。" + ex.Message +
+                   " content=" + content + ",charset=" + charset + ",privateKeySize=" + privateKey.Length;
+                Console.WriteLine(errorMessage);
+                throw new Exception(errorMessage, ex);
+            }
+        }
+
+        public bool Verify(string content, string charset, string publicKey, string sign)
+        {
+            try
+            {
+                ArgumentValidator.CheckNotNull(content, "待验签内容不可为Null");
+                ArgumentValidator.CheckArgument(!String.IsNullOrEmpty(publicKey), "公钥不可为空");
+                ArgumentValidator.CheckArgument(!String.IsNullOrEmpty(sign), "签名串不可为空");
+
+                if (String.IsNullOrEmpty(charset))
+                {
+                    charset = DEFAULT_CHARSET;
+                }
+                return DoVerify(content, charset, publicKey, sign);
+            }
+            catch (Exception ex)
+            {
+                String errorMessage = GetAsymmetricType() + "验签遭遇异常,请检查公钥格式是否正确。" + ex.Message +
+                   " content=" + content + ",charset=" + charset + ",publicKey=" + publicKey + ",sign=" + sign;
+                Console.WriteLine(errorMessage);
+                throw new Exception(errorMessage, ex);
+            }
+        }
+
+        protected abstract string DoDecrypt(string cipherTextBase64, string charset, string privateKey);
+
+        protected abstract string DoEncrypt(string plainText, string charset, string publicKey);
+
+        protected abstract string DoSign(string content, string charset, string privateKey);
+
+        protected abstract bool DoVerify(string content, string charset, string publicKey, string sign);
+
+        protected abstract string GetAsymmetricType();
+    }
+}

+ 47 - 0
PaySharp.Alipay/Util/Asymmetric/IAsymmetricEncryptor.cs

@@ -0,0 +1,47 @@
+using System;
+
+namespace PaySharp.Alipay.Util.Asymmetric
+{
+    /// <summary>
+    /// 非对称加密算法接口
+    /// </summary>
+    public interface IAsymmetricEncryptor
+    {
+        /// <summary>
+        /// 计算指定内容的签名
+        /// </summary>
+        /// <param name="content">待签名的原文</param>
+        /// <param name="charset">待签名的原文的字符集编码</param>
+        /// <param name="privateKey">私钥字符串</param>
+        /// <returns>签名字符串</returns>
+        string Sign(string content, string charset, string privateKey);
+
+        /// <summary>
+        /// 验证指定内容的签名是否正确
+        /// </summary>
+        /// <param name="content">待校验的原文</param>
+        /// <param name="charset">待校验的原文的字符集编码</param>
+        /// <param name="publicKey">公钥字符串</param>
+        /// <param name="sign">签名字符串</param>
+        /// <returns>true:验证通过;false:验证不通过</returns>
+        bool Verify(string content, string charset, string publicKey, string sign);
+
+        /// <summary>
+        /// 对明文进行非对称加密
+        /// </summary>
+        /// <param name="plainText">明文字符串</param>
+        /// <param name="charset">明文的字符集编码</param>
+        /// <param name="publicKey">公钥字符串</param>
+        /// <returns>密文的Base64编码字符串</returns>
+        string Encrypt(string plainText, string charset, string publicKey);
+
+        /// <summary>
+        /// 对密文进行非对称解密
+        /// </summary>
+        /// <param name="cipherTextBase64">密文Base64编码字符串</param>
+        /// <param name="charset">明文的字符集编码</param>
+        /// <param name="privateKey">私钥字符串</param>
+        /// <returns>明文</returns>
+        string Decrypt(string cipherTextBase64, string charset, string privateKey);
+    }
+}

+ 18 - 0
PaySharp.Alipay/Util/Asymmetric/RSA2Encryptor.cs

@@ -0,0 +1,18 @@
+namespace PaySharp.Alipay.Util.Asymmetric
+{
+    /// <summary>
+    /// RSA2算法加密器
+    /// 签名部分采用SHA256算法进行摘要计算,其余部分与RSA算法相同
+    /// </summary>
+    public class RSA2Encryptor : RSAEncryptor
+    {
+        /// <summary>
+        /// RSA2算法签名采用SHA256摘要算法
+        /// </summary>
+        /// <returns>摘要算法名称</returns>
+        protected override string GetShaType()
+        {
+            return "SHA256";
+        }
+    }
+}

+ 282 - 0
PaySharp.Alipay/Util/Asymmetric/RSAEncryptor.cs

@@ -0,0 +1,282 @@
+using System;
+using System.Text;
+using System.Security.Cryptography;
+using System.IO;
+
+namespace PaySharp.Alipay.Util.Asymmetric
+{
+    /// <summary>
+    /// RSA算法加密器
+    /// 签名部分采用SHA1算法进行摘要计算
+    /// </summary>
+    public class RSAEncryptor : BaseAsymmetricEncryptor
+    {
+        /// <summary>
+        /// RSA算法签名采用SHA1摘要算法
+        /// </summary>
+        /// <returns>摘要算法名称</returns>
+        protected virtual string GetShaType()
+        {
+            return "SHA1";
+        }
+
+        protected override string GetAsymmetricType()
+        {
+            return "RSA";
+        }
+
+        protected override string DoDecrypt(string cipherTextBase64, string charset, string privateKey)
+        {
+            using (RSACryptoServiceProvider rsaService = BuildRSAServiceProvider(Convert.FromBase64String(privateKey)))
+            {
+                byte[] data = Convert.FromBase64String(cipherTextBase64);
+
+                //解密块最大长度
+                int maxBlockSize = rsaService.KeySize / 8;
+
+                //如果密文长度小于等于单个解密块最大长度,直接单次调用解密接口完成解密
+                if (data.Length <= maxBlockSize)
+                {
+                    byte[] cipherbytes = rsaService.Decrypt(data, false);
+                    return Encoding.GetEncoding(charset).GetString(cipherbytes);
+                }
+
+                //如果密文长度大于单个解密块最大长度,在内存中循环调用解密接口完成解密
+                using (MemoryStream plainStream = new MemoryStream())
+                {
+                    using (MemoryStream cipherStream = new MemoryStream(data))
+                    {
+                        Byte[] buffer = new Byte[maxBlockSize];
+                        int readSize = cipherStream.Read(buffer, 0, maxBlockSize);
+                        while (readSize > 0)
+                        {
+                            Byte[] cipherBlock = new Byte[readSize];
+                            Array.Copy(buffer, 0, cipherBlock, 0, readSize);
+                            Byte[] plainBlock = rsaService.Decrypt(cipherBlock, false);
+                            plainStream.Write(plainBlock, 0, plainBlock.Length);
+                            readSize = cipherStream.Read(buffer, 0, maxBlockSize);
+                        }
+                    }
+                    return Encoding.GetEncoding(charset).GetString(plainStream.ToArray());
+                }
+
+            }
+        }
+
+        protected override string DoEncrypt(string plainText, string charset, string publicKey)
+        {
+            using (RSACryptoServiceProvider rsaService = new RSACryptoServiceProvider())
+            {
+                rsaService.PersistKeyInCsp = false;
+                rsaService.ImportParameters(ConvertFromPemPublicKey(publicKey));
+                byte[] data = Encoding.GetEncoding(charset).GetBytes(plainText);
+
+                //加密块最大长度
+                int maxBlockSize = rsaService.KeySize / 8 - 11;
+
+                //如果明文长度小于等于单个加密块最大长度,直接单次调用加密接口完成加密
+                if (data.Length <= maxBlockSize)
+                {
+                    byte[] cipherbytes = rsaService.Encrypt(data, false);
+                    return Convert.ToBase64String(cipherbytes);
+                }
+
+                //如果明文长度大于单个加密块最大长度,在内存中循环调用加密接口完成加密
+                using (MemoryStream cipherStream = new MemoryStream())
+                {
+                    using (MemoryStream plainStream = new MemoryStream(data))
+                    {
+                        Byte[] buffer = new Byte[maxBlockSize];
+                        int readSize = plainStream.Read(buffer, 0, maxBlockSize);
+                        while (readSize > 0)
+                        {
+                            Byte[] plainBlock = new Byte[readSize];
+                            Array.Copy(buffer, 0, plainBlock, 0, readSize);
+                            Byte[] cipherBlock = rsaService.Encrypt(plainBlock, false);
+                            cipherStream.Write(cipherBlock, 0, cipherBlock.Length);
+                            readSize = plainStream.Read(buffer, 0, maxBlockSize);
+                        }
+                    }
+                    return Convert.ToBase64String(cipherStream.ToArray(), Base64FormattingOptions.None);
+                }
+            }
+        }
+
+        protected override string DoSign(string content, string charset, string privateKey)
+        {
+            using (RSACryptoServiceProvider rsaService = BuildRSAServiceProvider(Convert.FromBase64String(privateKey)))
+            {
+                byte[] data = Encoding.GetEncoding(charset).GetBytes(content);
+
+                byte[] sign = rsaService.SignData(data, GetShaType());
+                return Convert.ToBase64String(sign);
+            }
+        }
+
+        protected override bool DoVerify(string content, string charset, string publicKey, string sign)
+        {
+            using (RSACryptoServiceProvider rsaService = new RSACryptoServiceProvider())
+            {
+                rsaService.PersistKeyInCsp = false;
+                rsaService.ImportParameters(ConvertFromPemPublicKey(publicKey));
+
+                return rsaService.VerifyData(Encoding.GetEncoding(charset).GetBytes(content),
+                    GetShaType(), Convert.FromBase64String(sign));
+            }
+        }
+
+        private RSAParameters ConvertFromPemPublicKey(string pemPublickKey)
+        {
+            if (string.IsNullOrEmpty(pemPublickKey))
+            {
+                throw new Exception("PEM格式公钥不可为空。");
+            }
+
+            //移除干扰文本
+            pemPublickKey = pemPublickKey.Replace("-----BEGIN PUBLIC KEY-----", "")
+                .Replace("-----END PUBLIC KEY-----", "").Replace("\n", "").Replace("\r", "");
+
+            byte[] keyData = Convert.FromBase64String(pemPublickKey);
+            bool keySize1024 = (keyData.Length == 162);
+            bool keySize2048 = (keyData.Length == 294);
+            if (!(keySize1024 || keySize2048))
+            {
+                throw new Exception("公钥长度只支持1024和2048。");
+            }
+            byte[] pemModulus = (keySize1024 ? new byte[128] : new byte[256]);
+            byte[] pemPublicExponent = new byte[3];
+            Array.Copy(keyData, (keySize1024 ? 29 : 33), pemModulus, 0, (keySize1024 ? 128 : 256));
+            Array.Copy(keyData, (keySize1024 ? 159 : 291), pemPublicExponent, 0, 3);
+            RSAParameters para = new RSAParameters();
+            para.Modulus = pemModulus;
+            para.Exponent = pemPublicExponent;
+            return para;
+        }
+
+        private static RSACryptoServiceProvider BuildRSAServiceProvider(byte[] privateKey)
+        {
+            byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
+            byte bt = 0;
+            ushort twobytes = 0;
+            int elems = 0;
+
+            //set up stream to decode the asn.1 encoded RSA private key
+            //wrap Memory Stream with BinaryReader for easy reading
+            using (BinaryReader binaryReader = new BinaryReader(new MemoryStream(privateKey)))
+            {
+                twobytes = binaryReader.ReadUInt16();
+                //data read as little endian order (actual data order for Sequence is 30 81)
+                if (twobytes == 0x8130)
+                {
+                    //advance 1 byte
+                    binaryReader.ReadByte();
+                }
+                else if (twobytes == 0x8230)
+                {
+                    //advance 2 bytes
+                    binaryReader.ReadInt16();
+                }
+                else
+                {
+                    return null;
+                }
+
+                twobytes = binaryReader.ReadUInt16();
+                //version number
+                if (twobytes != 0x0102)
+                {
+                    return null;
+                }
+                bt = binaryReader.ReadByte();
+                if (bt != 0x00)
+                {
+                    return null;
+                }
+
+                //all private key components are Integer sequences
+                elems = GetIntegerSize(binaryReader);
+                MODULUS = binaryReader.ReadBytes(elems);
+
+                elems = GetIntegerSize(binaryReader);
+                E = binaryReader.ReadBytes(elems);
+
+                elems = GetIntegerSize(binaryReader);
+                D = binaryReader.ReadBytes(elems);
+
+                elems = GetIntegerSize(binaryReader);
+                P = binaryReader.ReadBytes(elems);
+
+                elems = GetIntegerSize(binaryReader);
+                Q = binaryReader.ReadBytes(elems);
+
+                elems = GetIntegerSize(binaryReader);
+                DP = binaryReader.ReadBytes(elems);
+
+                elems = GetIntegerSize(binaryReader);
+                DQ = binaryReader.ReadBytes(elems);
+
+                elems = GetIntegerSize(binaryReader);
+                IQ = binaryReader.ReadBytes(elems);
+
+                //create RSACryptoServiceProvider instance and initialize with public key
+                RSACryptoServiceProvider rsaService = new RSACryptoServiceProvider();
+                RSAParameters rsaParams = new RSAParameters
+                {
+                    Modulus = MODULUS,
+                    Exponent = E,
+                    D = D,
+                    P = P,
+                    Q = Q,
+                    DP = DP,
+                    DQ = DQ,
+                    InverseQ = IQ
+                };
+                rsaService.ImportParameters(rsaParams);
+                return rsaService;
+            }
+        }
+
+        private static int GetIntegerSize(BinaryReader binaryReader)
+        {
+            byte bt = 0;
+            byte lowbyte = 0x00;
+            byte highbyte = 0x00;
+            int count = 0;
+
+            bt = binaryReader.ReadByte();
+
+            //expect integer
+            if (bt != 0x02)
+            {
+                return 0;
+            }
+            bt = binaryReader.ReadByte();
+
+            if (bt == 0x81)
+            {
+                //data size in next byte
+                count = binaryReader.ReadByte();
+            }
+            else if (bt == 0x82)
+            {
+                //data size in next 2 bytes
+                highbyte = binaryReader.ReadByte();
+                lowbyte = binaryReader.ReadByte();
+                byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
+                count = BitConverter.ToInt32(modint, 0);
+            }
+            else
+            {
+                //we already have the data size
+                count = bt;
+            }
+            while (binaryReader.ReadByte() == 0x00)
+            {   //remove high order zeros in data
+                count -= 1;
+            }
+            //last ReadByte wasn't a removed zero, so back up a byte
+            binaryReader.BaseStream.Seek(-1, SeekOrigin.Current);
+            return count;
+        }
+    }
+}

+ 110 - 0
PaySharp.Alipay/Util/Asymmetric/SM2Encryptor.cs

@@ -0,0 +1,110 @@
+using System;
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto;
+using System.Text;
+using Org.BouncyCastle.Crypto.Engines;
+using Org.BouncyCastle.Crypto.Signers;
+using Org.BouncyCastle.Security;
+
+namespace PaySharp.Alipay.Util.Asymmetric
+{
+    /// <summary>
+    /// 国密SM2算法(ECC算法)加密器
+    /// 签名部分采用SM3算法进行摘要计算
+    /// </summary>
+    public class SM2Encryptor : BaseAsymmetricEncryptor
+    {
+        /// <summary>
+        /// SM2算法默认用户ID,目前开放平台不会使用非默认用户ID
+        /// </summary>
+        public const string DEFAULT_USER_ID = "1234567812345678";
+
+        protected override string GetAsymmetricType()
+        {
+            return "SM2";
+        }
+
+        protected override string DoDecrypt(string cipherTextBase64, string charset, string privateKey)
+        {
+            //加载私钥参数
+            ICipherParameters cipherParams = BuildPrivateKeyParams(privateKey).Parameters;
+
+            //初始化SM2算法引擎
+            SM2Engine sm2Engine = new SM2Engine();
+            sm2Engine.Init(false, cipherParams);
+
+            //对输入密文进行解密
+            byte[] input = Convert.FromBase64String(cipherTextBase64);
+            byte[] output = sm2Engine.ProcessBlock(input, 0, input.Length);
+
+            //将解密后的明文按指定字符集编码后返回
+            return Encoding.GetEncoding(charset).GetString(output);
+        }
+
+        protected override string DoEncrypt(string plainText, string charset, string publicKey)
+        {
+            //加载公钥参数
+            ICipherParameters cipherParams = BuildPublickKeyParams(publicKey).Parameters;
+            ParametersWithRandom parametersWithRandom = new ParametersWithRandom(cipherParams);
+
+            //初始化SM2算法引擎
+            SM2Engine sm2Engine = new SM2Engine();
+            sm2Engine.Init(true, parametersWithRandom);
+
+            //对输入明文进行加密
+            byte[] input = Encoding.GetEncoding(charset).GetBytes(plainText);
+            byte[] output = sm2Engine.ProcessBlock(input, 0, input.Length);
+
+            //将密文Base64编码后返回
+            return Convert.ToBase64String(output);
+        }
+
+        protected override string DoSign(string content, string charset, string privateKey)
+        {
+            //加载私钥参数
+            ParametersWithID parametersWithID = BuildPrivateKeyParams(privateKey);
+
+            //加载签名器
+            SM2Signer signer = new SM2Signer();
+            signer.Init(true, parametersWithID);
+
+            //向签名器中输入原文
+            byte[] input = Encoding.GetEncoding(charset).GetBytes(content);
+            signer.BlockUpdate(input, 0, input.Length);
+
+            //将签名结果转换为Base64
+            return Convert.ToBase64String(signer.GenerateSignature());
+        }
+
+        protected override bool DoVerify(string content, string charset, string publicKey, string sign)
+        {
+            //加载公钥参数
+            ParametersWithID parametersWithID = BuildPublickKeyParams(publicKey);
+
+            //加载签名器
+            SM2Signer signer = new SM2Signer();
+            signer.Init(false, parametersWithID);
+
+            //向签名器中输入原文
+            byte[] input = Encoding.GetEncoding(charset).GetBytes(content);
+            signer.BlockUpdate(input, 0, input.Length);
+
+            //传入指定签名串进行验签并返回结果
+            return signer.VerifySignature(Convert.FromBase64String(sign));
+        }
+
+        private ParametersWithID BuildPrivateKeyParams(string privateKey)
+        {
+            AsymmetricKeyParameter key = PrivateKeyFactory.CreateKey(Convert.FromBase64String(privateKey));
+            ParametersWithID parametersWithID = new ParametersWithID(key, Encoding.UTF8.GetBytes(DEFAULT_USER_ID));
+            return parametersWithID;
+        }
+
+        private static ParametersWithID BuildPublickKeyParams(string publicKey)
+        {
+            AsymmetricKeyParameter key = PublicKeyFactory.CreateKey(Convert.FromBase64String(publicKey));
+            ParametersWithID parametersWithID = new ParametersWithID(key, Encoding.UTF8.GetBytes(DEFAULT_USER_ID));
+            return parametersWithID;
+        }
+    }
+}

+ 23 - 0
PaySharp.Alipay/Util/SignSourceData.cs

@@ -0,0 +1,23 @@
+namespace PaySharp.Alipay.Util
+{
+    /// <summary>
+    /// 从响应字符串中提取到的待验签原始内容
+    /// </summary>
+    public class SignSourceData
+    {
+        /// <summary>
+        /// 待验签原始内容
+        /// </summary>
+        public string SourceData { get; set; }
+
+        /// <summary>
+        /// 待验签原始内容在响应字符串中的起始位置
+        /// </summary>
+        public int BeginIndex { get; set; }
+
+        /// <summary>
+        /// 待验签原始内容在响应字符串中的结束位置
+        /// </summary>
+        public int EndIndex { get; set; }
+    }
+}

+ 11 - 0
PaySharp.Core/Attributes/IgnoreAttribute.cs

@@ -0,0 +1,11 @@
+using System;
+
+namespace PaySharp.Core
+{
+    /// <summary>
+    /// 不添加到网关参数属性
+    /// </summary>
+    public class IgnoreAttribute : Attribute
+    {
+    }
+}

+ 12 - 0
PaySharp.Core/Attributes/ReNameAttribute.cs

@@ -0,0 +1,12 @@
+using System;
+
+namespace PaySharp.Core
+{
+    /// <summary>
+    /// 重命名属性
+    /// </summary>
+    public class ReNameAttribute : Attribute
+    {
+        public string Name { get; set; }
+    }
+}

+ 35 - 0
PaySharp.Core/ConfigurationHandler.cs

@@ -0,0 +1,35 @@
+#if NET45
+using System.Collections;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Xml;
+
+namespace PaySharp.Core
+{
+    public class ConfigurationHandler : IConfigurationSectionHandler
+    {
+        public object Create(object parent, object configContext, XmlNode section)
+        {
+            var list = new List<Hashtable>();
+
+            foreach (XmlNode child in section.ChildNodes)
+            {
+                var hashtable = new Hashtable();
+                if (child.Attributes["gatewayUrl"] != null)
+                {
+                    hashtable.Add("gatewayUrl", child.Attributes["gatewayUrl"].Value);
+                }
+
+                foreach (XmlNode grandChild in child.ChildNodes)
+                {
+                    hashtable.Add(grandChild.Name, grandChild.InnerText);
+                }
+
+                list.Add(hashtable);
+            }
+
+            return list;
+        }
+    }
+}
+#endif

+ 18 - 0
PaySharp.Core/Events/CancelSucceedEventArgs.cs

@@ -0,0 +1,18 @@
+namespace PaySharp.Core
+{
+    public class CancelSucceedEventArgs : NotifyEventArgs
+    {
+        #region 构造函数
+
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        /// <param name="gateway">支付网关</param>
+        public CancelSucceedEventArgs(BaseGateway gateway)
+            : base(gateway)
+        {
+        }
+
+        #endregion
+    }
+}

+ 61 - 0
PaySharp.Core/Events/NotifyEventArgs.cs

@@ -0,0 +1,61 @@
+using System;
+using PaySharp.Core.Response;
+using PaySharp.Core.Utils;
+
+namespace PaySharp.Core
+{
+    /// <summary>
+    /// 事件数据的基类
+    /// </summary>
+    public abstract class NotifyEventArgs : EventArgs
+    {
+        #region 私有字段
+
+        protected BaseGateway _gateway;
+
+        #endregion
+
+        #region 构造函数
+
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        /// <param name="gateway">支付网关</param>
+        protected NotifyEventArgs(BaseGateway gateway)
+        {
+            _gateway = gateway;
+            NotifyServerHostAddress = HttpUtil.RemoteIpAddress;
+        }
+
+        #endregion
+
+        #region 属性
+
+        /// <summary>
+        /// 发送支付通知的网关IP地址
+        /// </summary>
+        public string NotifyServerHostAddress { get; private set; }
+
+        /// <summary>
+        /// 网关的数据
+        /// </summary>
+        public GatewayData GatewayData => _gateway.GatewayData;
+
+        /// <summary>
+        /// 网关类型
+        /// </summary>
+        public Type GatewayType => _gateway.GetType();
+
+        /// <summary>
+        /// 通知数据
+        /// </summary>
+        public IResponse NotifyResponse => _gateway.NotifyResponse;
+
+        /// <summary>
+        /// 通知类型
+        /// </summary>
+        public NotifyType NotifyType => HttpUtil.RequestType == "GET" ? NotifyType.Sync : NotifyType.Async;
+
+        #endregion
+    }
+}

+ 21 - 0
PaySharp.Core/Events/PaySucceedEventArgs.cs

@@ -0,0 +1,21 @@
+namespace PaySharp.Core
+{
+    /// <summary>
+    /// 支付成功网关事件数据
+    /// </summary>
+    public class PaySucceedEventArgs : NotifyEventArgs
+    {
+        #region 构造函数
+
+        /// <summary>
+        /// 初始化支付成功网关事件数据
+        /// </summary>
+        /// <param name="gateway">支付网关</param>
+        public PaySucceedEventArgs(BaseGateway gateway)
+            : base(gateway)
+        {
+        }
+
+        #endregion
+    }
+}

+ 18 - 0
PaySharp.Core/Events/RefundSucceedEventArgs.cs

@@ -0,0 +1,18 @@
+namespace PaySharp.Core
+{
+    public class RefundSucceedEventArgs : NotifyEventArgs
+    {
+        #region 构造函数
+
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        /// <param name="gateway">支付网关</param>
+        public RefundSucceedEventArgs(BaseGateway gateway)
+            : base(gateway)
+        {
+        }
+
+        #endregion
+    }
+}

+ 24 - 0
PaySharp.Core/Events/UnKnownNotifyEventArgs.cs

@@ -0,0 +1,24 @@
+namespace PaySharp.Core
+{
+    public class UnKnownNotifyEventArgs : NotifyEventArgs
+    {
+        #region 构造函数
+
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        /// <param name="gateway">支付网关</param>
+        public UnKnownNotifyEventArgs(BaseGateway gateway)
+            : base(gateway)
+        {
+        }
+
+        #endregion
+
+        #region 属性
+
+        public string Message { get; set; }
+
+        #endregion
+    }
+}

+ 21 - 0
PaySharp.Core/Events/UnknownGatewayEventArgs.cs

@@ -0,0 +1,21 @@
+namespace PaySharp.Core
+{
+    /// <summary>
+    /// 未知网关事件数据
+    /// </summary>
+    public class UnknownGatewayEventArgs : NotifyEventArgs
+    {
+        #region 构造函数
+
+        /// <summary>
+        /// 初始化未知网关事件数据
+        /// </summary>
+        /// <param name="gateway">支付网关</param>
+        public UnknownGatewayEventArgs(BaseGateway gateway)
+            : base(gateway)
+        {
+        }
+
+        #endregion
+    }
+}

+ 12 - 0
PaySharp.Core/Exceptions/GatewayException.cs

@@ -0,0 +1,12 @@
+using System;
+
+namespace PaySharp.Core.Exceptions
+{
+    public class GatewayException : Exception
+    {
+        public GatewayException(string message)
+            : base(message)
+        {
+        }
+    }
+}

+ 112 - 0
PaySharp.Core/Gateways/BaseGateway.cs

@@ -0,0 +1,112 @@
+using System.Threading.Tasks;
+using PaySharp.Core.Request;
+using PaySharp.Core.Response;
+using PaySharp.Core.Utils;
+
+namespace PaySharp.Core
+{
+    /// <summary>
+    /// 网关的抽象基类
+    /// </summary>
+    public abstract class BaseGateway : IGateway
+    {
+        #region 构造函数
+
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        protected BaseGateway()
+        {
+        }
+
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        /// <param name="merchant">商户数据</param>
+        protected BaseGateway(IMerchant merchant)
+        {
+            Merchant = merchant;
+        }
+
+        #endregion
+
+        #region 属性
+
+        /// <summary>
+        /// 商户数据
+        /// </summary>
+        public IMerchant Merchant { get; set; }
+
+        /// <summary>
+        /// 通知数据
+        /// </summary>
+        public IResponse NotifyResponse { get; set; }
+
+        /// <summary>
+        /// 网关的地址
+        /// </summary>
+        public abstract string GatewayUrl { get; set; }
+
+        /// <summary>
+        /// 网关数据
+        /// </summary>
+        protected internal GatewayData GatewayData { get; set; }
+
+        /// <summary>
+        /// 是否支付成功
+        /// </summary>
+        protected internal abstract bool IsPaySuccess { get; }
+
+        /// <summary>
+        /// 是否退款成功
+        /// </summary>
+        protected internal abstract bool IsRefundSuccess { get; }
+
+        /// <summary>
+        /// 是否撤销成功
+        /// </summary>
+        protected internal abstract bool IsCancelSuccess { get; }
+
+        /// <summary>
+        /// 需要验证的参数名称数组,用于识别不同的网关类型。
+        /// 商户号(AppId)必须放第一位
+        /// </summary>
+        protected internal abstract string[] NotifyVerifyParameter { get; }
+
+        #endregion
+
+        #region 方法
+
+        /// <summary>
+        /// 检验网关返回的通知,确认订单是否支付成功
+        /// </summary>
+        protected internal abstract Task<bool> ValidateNotifyAsync();
+
+        /// <summary>
+        /// 当接收到支付网关通知并验证无误时按照支付网关要求格式输出表示成功接收到网关通知的字符串
+        /// </summary>
+        protected internal virtual void WriteSuccessFlag()
+        {
+            HttpUtil.Write("success");
+        }
+
+        /// <summary>
+        /// 当接收到支付网关通知并验证有误时按照支付网关要求格式输出表示失败接收到网关通知的字符串
+        /// </summary>
+        protected internal virtual void WriteFailureFlag()
+        {
+            HttpUtil.Write("failure");
+        }
+
+        /// <summary>
+        /// 执行请求
+        /// </summary>
+        /// <typeparam name="TModel">数据模型</typeparam>
+        /// <typeparam name="TResponse">返回模型</typeparam>
+        /// <param name="request">请求</param>
+        /// <returns></returns>
+        public abstract TResponse Execute<TModel, TResponse>(Request<TModel, TResponse> request) where TResponse : IResponse;
+
+        #endregion
+    }
+}

+ 595 - 0
PaySharp.Core/Gateways/GatewayData.cs

@@ -0,0 +1,595 @@
+#if NETCOREAPP3_1
+using Microsoft.AspNetCore.Http;
+#endif
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Net;
+using System.Reflection;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using System.Xml;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using PaySharp.Core.Utils;
+
+namespace PaySharp.Core
+{
+    /// <summary>
+    /// 网关数据
+    /// </summary>
+    public class GatewayData
+    {
+        #region 私有字段
+
+        private readonly SortedDictionary<string, object> _values;
+
+        #endregion
+
+        #region 属性
+
+        public object this[string key]
+        {
+            get => _values[key];
+            set => _values[key] = value;
+        }
+
+        public SortedDictionary<string, object>.KeyCollection Keys => _values.Keys;
+
+        public SortedDictionary<string, object>.ValueCollection Values => _values.Values;
+
+        public KeyValuePair<string, object> this[int index] => _values.ElementAt(index);
+
+        public int Count => _values.Count;
+
+        /// <summary>
+        /// 原始值
+        /// </summary>
+        public string Raw { get; set; }
+
+        #endregion
+
+        #region 构造函数
+
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        public GatewayData()
+        {
+            _values = new SortedDictionary<string, object>();
+        }
+
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        /// <param name="comparer">排序策略</param>
+        public GatewayData(IComparer<string> comparer)
+        {
+            _values = new SortedDictionary<string, object>(comparer);
+        }
+
+        #endregion
+
+        #region 方法
+
+        /// <summary>
+        /// 添加参数
+        /// </summary>
+        /// <param name="key">参数名</param>
+        /// <param name="value">参数值</param>
+        /// <returns></returns>
+        public bool Add(string key, object value)
+        {
+            Raw = null;
+            if (string.IsNullOrEmpty(key))
+            {
+                throw new ArgumentNullException("key", "参数名不能为空");
+            }
+
+            if (value is null || string.IsNullOrEmpty(value.ToString()))
+            {
+                return false;
+            }
+
+            if (Exists(key))
+            {
+                _values[key] = value;
+            }
+            else
+            {
+                _values.Add(key, value);
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// 添加参数
+        /// </summary>
+        /// <param name="obj">对象</param>
+        /// <param name="stringCase">字符串策略</param>
+        /// <returns></returns>
+        public bool Add(object obj, StringCase stringCase)
+        {
+            ValidateUtil.Validate(obj, null);
+
+            Raw = null;
+            var type = obj.GetType();
+            var properties = type.GetProperties();
+            var fields = type.GetFields();
+
+            Add(obj, properties, stringCase);
+            Add(obj, fields, stringCase);
+
+            return true;
+        }
+
+        private void Add(object obj, MemberInfo[] info, StringCase stringCase)
+        {
+            foreach (var item in info)
+            {
+                var notAddattributes = item.GetCustomAttributes(typeof(IgnoreAttribute), true);
+                if (notAddattributes.Length > 0)
+                {
+                    continue;
+                }
+
+                string key;
+                var renameAttribute = item.GetCustomAttributes(typeof(ReNameAttribute), true);
+                if (renameAttribute.Length > 0)
+                {
+                    key = ((ReNameAttribute)renameAttribute[0]).Name;
+                }
+                else
+                {
+                    key = stringCase switch
+                    {
+                        StringCase.Snake => item.Name.ToSnakeCase(),
+                        StringCase.Camel => item.Name.ToCamelCase(),
+                        StringCase.Lower => item.Name.ToLower(),
+                        _ => item.Name,
+                    };
+                }
+
+                var value = item.MemberType switch
+                {
+                    MemberTypes.Field => ((FieldInfo)item).GetValue(obj),
+                    MemberTypes.Property => ((PropertyInfo)item).GetValue(obj),
+                    _ => throw new NotImplementedException(),
+                };
+                if (value is null || string.IsNullOrEmpty(value.ToString()))
+                {
+                    continue;
+                }
+
+                if (Exists(key))
+                {
+                    _values[key] = value;
+                }
+                else
+                {
+                    _values.Add(key, value);
+                }
+            }
+        }
+
+        /// <summary>
+        /// 根据参数名获取参数值
+        /// </summary>
+        /// <param name="key">参数名</param>
+        /// <returns>参数值</returns>
+        public object GetValue(string key)
+        {
+            _values.TryGetValue(key, out var value);
+            return value;
+        }
+
+        /// <summary>
+        /// 根据参数名获取参数值
+        /// </summary>
+        /// <param name="key">参数名</param>
+        /// <returns>参数值</returns>
+        public string GetStringValue(string key)
+        {
+            return GetValue(key)?.ToString();
+        }
+
+        /// <summary>
+        /// 根据参数名获取参数值
+        /// </summary>
+        /// <param name="key">参数名</param>
+        /// <returns>参数值</returns>
+        public double GetDoubleValue(string key)
+        {
+            double.TryParse(GetStringValue(key), out var value);
+            return value;
+        }
+
+        /// <summary>
+        /// 根据参数名获取参数值
+        /// </summary>
+        /// <param name="key">参数名</param>
+        /// <returns>参数值</returns>
+        public int GetIntValue(string key)
+        {
+            int.TryParse(GetStringValue(key), out var value);
+            return value;
+        }
+
+        /// <summary>
+        /// 根据参数名获取参数值
+        /// </summary>
+        /// <param name="key">参数名</param>
+        /// <returns>参数值</returns>
+        public DateTime GetDateTimeValue(string key)
+        {
+            DateTime.TryParse(GetStringValue(key), out var value);
+            return value;
+        }
+
+        /// <summary>
+        /// 根据参数名获取参数值
+        /// </summary>
+        /// <param name="key">参数名</param>
+        /// <returns>参数值</returns>
+        public float GetFloatValue(string key)
+        {
+            float.TryParse(GetStringValue(key), out var value);
+            return value;
+        }
+
+        /// <summary>
+        /// 根据参数名获取参数值
+        /// </summary>
+        /// <param name="key">参数名</param>
+        /// <returns>参数值</returns>
+        public decimal GetDecimalValue(string key)
+        {
+            decimal.TryParse(GetStringValue(key), out var value);
+            return value;
+        }
+
+        /// <summary>
+        /// 根据参数名获取参数值
+        /// </summary>
+        /// <param name="key">参数名</param>
+        /// <returns>参数值</returns>
+        public byte GetByteValue(string key)
+        {
+            byte.TryParse(GetStringValue(key), out var value);
+            return value;
+        }
+
+        /// <summary>
+        /// 根据参数名获取参数值
+        /// </summary>
+        /// <param name="key">参数名</param>
+        /// <returns>参数值</returns>
+        public char GetCharValue(string key)
+        {
+            char.TryParse(GetStringValue(key), out var value);
+            return value;
+        }
+
+        /// <summary>
+        /// 根据参数名获取参数值
+        /// </summary>
+        /// <param name="key">参数名</param>
+        /// <returns>参数值</returns>
+        public bool GetBoolValue(string key)
+        {
+            bool.TryParse(GetStringValue(key), out var value);
+            return value;
+        }
+
+        /// <summary>
+        /// 是否存在指定参数名
+        /// </summary>
+        /// <param name="key">参数名</param>
+        /// <returns></returns>
+        public bool Exists(string key) => _values.ContainsKey(key);
+
+        /// <summary>
+        /// 将网关数据转成Xml格式数据
+        /// </summary>
+        /// <returns></returns>
+        public string ToXml()
+        {
+            if (_values.Count == 0)
+            {
+                return string.Empty;
+            }
+
+            var sb = new StringBuilder();
+            sb.Append("<xml>");
+            foreach (var item in _values)
+            {
+                if (item.Value is string)
+                {
+                    sb.AppendFormat("<{0}><![CDATA[{1}]]></{0}>", item.Key, item.Value);
+
+                }
+                else
+                {
+                    sb.AppendFormat("<{0}>{1}</{0}>", item.Key, item.Value);
+                }
+            }
+            sb.Append("</xml>");
+
+            return sb.ToString();
+        }
+
+        /// <summary>
+        /// 将Xml格式数据转换为网关数据
+        /// </summary>
+        /// <param name="xml">Xml数据</param>
+        /// <returns></returns>
+        public void FromXml(string xml)
+        {
+            try
+            {
+                Clear();
+                if (!string.IsNullOrEmpty(xml))
+                {
+                    var xmlDoc = new XmlDocument()
+                    {
+                        XmlResolver = null
+                    };
+                    xmlDoc.LoadXml(xml);
+                    var xmlElement = xmlDoc.DocumentElement;
+                    var nodes = xmlElement.ChildNodes;
+                    foreach (var item in nodes)
+                    {
+                        var xe = (XmlElement)item;
+                        Add(xe.Name, xe.InnerText);
+                    }
+                }
+            }
+            finally
+            {
+                Raw = xml;
+            }
+        }
+
+        /// <summary>
+        /// 将网关数据转换为Url格式数据
+        /// </summary>
+        /// <param name="isUrlEncode">是否需要url编码</param>
+        /// <returns></returns>
+        public string ToUrl(bool isUrlEncode = true)
+        {
+            return string.Join("&",
+                _values
+                .Select(a => $"{a.Key}={(isUrlEncode ? WebUtility.UrlEncode(a.Value.ToString()) : a.Value.ToString())}"));
+        }
+
+        /// <summary>
+        /// 将Url格式数据转换为网关数据
+        /// </summary>
+        /// <param name="url">url数据</param>
+        /// <param name="isUrlDecode">是否需要url解码</param>
+        /// <returns></returns>
+        public void FromUrl(string url, bool isUrlDecode = true)
+        {
+            try
+            {
+                Clear();
+                if (!string.IsNullOrEmpty(url))
+                {
+                    var index = url.IndexOf('?');
+
+                    if (index == 0)
+                    {
+                        url = url.Substring(index + 1);
+                    }
+
+                    var regex = new Regex(@"(^|&)?(\w+)=([^&]+)(&|$)?", RegexOptions.Compiled);
+                    var mc = regex.Matches(url);
+
+                    foreach (Match item in mc)
+                    {
+                        var value = item.Result("$3");
+                        Add(item.Result("$2"), isUrlDecode ? WebUtility.UrlDecode(value) : value);
+                    }
+                }
+            }
+            finally
+            {
+                Raw = url;
+            }
+        }
+
+#if NETCOREAPP3_1
+
+        /// <summary>
+        /// 将表单数据转换为网关数据
+        /// </summary>
+        /// <param name="form">表单</param>
+        /// <returns></returns>
+        public void FromForm(IFormCollection form)
+        {
+            try
+            {
+                Clear();
+                var allKeys = form.Keys;
+
+                foreach (var item in allKeys)
+                {
+                    Add(item, form[item]);
+                }
+            }
+            catch { }
+        }
+#endif
+
+        /// <summary>
+        /// 将键值对转换为网关数据
+        /// </summary>
+        /// <param name="nvc">键值对</param>
+        /// <returns></returns>
+        public void FromNameValueCollection(NameValueCollection nvc)
+        {
+            foreach (var item in nvc.AllKeys)
+            {
+                Add(item, nvc[item]);
+            }
+        }
+
+        /// <summary>
+        /// 将网关数据转换为表单数据
+        /// </summary>
+        /// <param name="url">请求地址</param>
+        /// <returns></returns>
+        public string ToForm(string url)
+        {
+            var html = new StringBuilder();
+            html.AppendLine("<body>");
+            html.AppendLine($"<form name='gateway' method='post' action ='{url}'>");
+            foreach (var item in _values)
+            {
+                html.AppendLine($"<input type='hidden' name='{item.Key}' value='{item.Value}'>");
+            }
+            html.AppendLine("</form>");
+            html.AppendLine("<script language='javascript' type='text/javascript'>");
+            html.AppendLine("document.gateway.submit();");
+            html.AppendLine("</script>");
+            html.AppendLine("</body>");
+
+            return html.ToString();
+        }
+
+        /// <summary>
+        /// 将网关数据转成Json格式数据
+        /// </summary>
+        /// <returns></returns>
+        public string ToJson()
+        {
+            return JsonConvert.SerializeObject(_values);
+        }
+
+        /// <summary>
+        /// 将Json格式数据转成网关数据
+        /// </summary>
+        /// <param name="json">json数据</param>
+        /// <returns></returns>
+        public void FromJson(string json)
+        {
+            try
+            {
+                Clear();
+                if (!string.IsNullOrEmpty(json))
+                {
+                    var jObject = JObject.Parse(json);
+                    foreach (var item in jObject)
+                    {
+                        if (item.Value.Type == JTokenType.Object)
+                        {
+                            Add(item.Key, item.Value.ToString(Newtonsoft.Json.Formatting.None));
+                        }
+                        else
+                        {
+                            Add(item.Key, item.Value.ToString());
+                        }
+                    }
+                }
+            }
+            finally
+            {
+                Raw = json;
+            }
+        }
+
+        /// <summary>
+        /// 将网关参数转为类型
+        /// </summary>
+        /// <typeparam name="T">类型</typeparam>
+        /// <param name="stringCase">字符串策略</param>
+        /// <returns></returns>
+        public T ToObject<T>(StringCase stringCase)
+        {
+            var type = typeof(T);
+            var obj = Activator.CreateInstance(type);
+            var properties = type.GetProperties();
+
+            foreach (var item in properties)
+            {
+                var renameAttribute = item.GetCustomAttributes(typeof(ReNameAttribute), true);
+
+                string key;
+                if (renameAttribute.Length > 0)
+                {
+                    key = ((ReNameAttribute)renameAttribute[0]).Name;
+                }
+                else
+                {
+                    key = stringCase switch
+                    {
+                        StringCase.Snake => item.Name.ToSnakeCase(),
+                        StringCase.Camel => item.Name.ToCamelCase(),
+                        StringCase.Lower => item.Name.ToLower(),
+                        _ => item.Name
+                    };
+                }
+
+                var value = GetStringValue(key);
+                if (value == null)
+                {
+                    continue;
+                }
+
+                var propertyType = item.PropertyType;
+                if (propertyType.IsEnum)
+                {
+                    item.SetValue(obj, Enum.Parse(propertyType, value));
+                }
+                else if (propertyType.IsGenericType)
+                {
+                    propertyType = propertyType.GenericTypeArguments[0];
+                    if (propertyType.IsEnum)
+                    {
+                        item.SetValue(obj, Enum.Parse(propertyType, value));
+                    }
+                }
+                else
+                {
+                    item.SetValue(obj, Convert.ChangeType(value, propertyType));
+                }
+            }
+
+            return (T)obj;
+        }
+
+        /// <summary>
+        /// 异步将网关参数转为类型
+        /// </summary>
+        /// <typeparam name="T">类型</typeparam>
+        /// <param name="stringCase">字符串策略</param>
+        /// <returns></returns>
+        public async Task<T> ToObjectAsync<T>(StringCase stringCase)
+        {
+            return await Task.Run(() => ToObject<T>(stringCase));
+        }
+
+        /// <summary>
+        /// 清空网关数据
+        /// </summary>
+        public void Clear()
+        {
+            _values.Clear();
+        }
+
+        /// <summary>
+        /// 移除指定参数
+        /// </summary>
+        /// <param name="key">参数名</param>
+        /// <returns></returns>
+        public bool Remove(string key)
+        {
+            return _values.Remove(key);
+        }
+
+        #endregion
+    }
+}

+ 114 - 0
PaySharp.Core/Gateways/Gateways.cs

@@ -0,0 +1,114 @@
+using System.Collections.Generic;
+using System.Linq;
+using PaySharp.Core.Exceptions;
+
+namespace PaySharp.Core
+{
+    /// <summary>
+    /// 网关集合类
+    /// </summary>
+    public class Gateways : IGateways
+    {
+        #region 私有字段
+
+        private readonly ICollection<BaseGateway> _list;
+
+        #endregion
+
+        #region 属性
+
+        /// <summary>
+        /// 网关数量
+        /// </summary>
+        public int Count => _list.Count;
+
+        #endregion
+
+        #region 构造函数
+
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        public Gateways()
+        {
+            _list = new List<BaseGateway>();
+        }
+
+        #endregion
+
+        #region 方法
+
+        /// <summary>
+        /// 添加网关
+        /// </summary>
+        /// <param name="gateway">网关</param>
+        /// <returns></returns>
+        public bool Add(BaseGateway gateway)
+        {
+            if (gateway != null)
+            {
+                if (!Exist(gateway.Merchant.AppId))
+                {
+                    _list.Add(gateway);
+
+                    return true;
+                }
+                else
+                {
+                    throw new GatewayException("该商户数据已存在");
+                }
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// 获取指定网关
+        /// </summary>
+        /// <typeparam name="T">网关类型</typeparam>
+        /// <returns></returns>
+        public BaseGateway Get<T>()
+        {
+            var gatewayList = _list
+                .Where(a => a is T)
+                .ToList();
+
+            return gatewayList.Count > 0 ? gatewayList[0] : throw new GatewayException("找不到指定网关");
+        }
+
+        /// <summary>
+        /// 通过AppId获取网关
+        /// </summary>
+        /// <typeparam name="T">网关类型</typeparam>
+        /// <param name="appId">AppId</param>
+        /// <returns></returns>
+        public BaseGateway Get<T>(string appId)
+        {
+            var gatewayList = _list
+                .Where(a => a is T && a.Merchant.AppId == appId)
+                .ToList();
+
+            var gateway = gatewayList.Count > 0 ? gatewayList[0] : throw new GatewayException("找不到指定网关");
+
+            return gateway;
+        }
+
+        /// <summary>
+        /// 指定AppId是否存在
+        /// </summary>
+        /// <param name="appId">appId</param>
+        /// <returns></returns>
+        private bool Exist(string appId) => _list.Any(a => a.Merchant.AppId == appId);
+
+        /// <summary>
+        /// 获取网关列表
+        /// </summary>
+        /// <returns></returns>
+        public ICollection<BaseGateway> GetList()
+        {
+            return _list;
+        }
+
+        #endregion
+    }
+}

+ 10 - 0
PaySharp.Core/Gateways/IGateway.cs

@@ -0,0 +1,10 @@
+using PaySharp.Core.Request;
+using PaySharp.Core.Response;
+
+namespace PaySharp.Core
+{
+    public interface IGateway
+    {
+        TResponse Execute<TModel, TResponse>(Request<TModel, TResponse> request) where TResponse : IResponse;
+    }
+}

+ 35 - 0
PaySharp.Core/Gateways/IGateways.cs

@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+
+namespace PaySharp.Core
+{
+    public interface IGateways
+    {
+        /// <summary>
+        /// 添加网关
+        /// </summary>
+        /// <param name="gateway">网关</param>
+        /// <returns></returns>
+        bool Add(BaseGateway gateway);
+
+        /// <summary>
+        /// 获取指定网关
+        /// </summary>
+        /// <typeparam name="T">网关类型</typeparam>
+        /// <returns></returns>
+        BaseGateway Get<T>();
+
+        /// <summary>
+        /// 通过AppId获取网关
+        /// </summary>
+        /// <typeparam name="T">网关类型</typeparam>
+        /// <param name="appId">AppId</param>
+        /// <returns></returns>
+        BaseGateway Get<T>(string appId);
+
+        /// <summary>
+        /// 获取网关列表
+        /// </summary>
+        /// <returns></returns>
+        ICollection<BaseGateway> GetList();
+    }
+}

+ 32 - 0
PaySharp.Core/Gateways/NullGateway.cs

@@ -0,0 +1,32 @@
+using System;
+using System.Threading.Tasks;
+using PaySharp.Core.Request;
+
+namespace PaySharp.Core
+{
+    /// <summary>
+    /// 未知网关
+    /// </summary>
+    public class NullGateway : BaseGateway
+    {
+        public override string GatewayUrl { get; set; }
+
+        protected internal override bool IsPaySuccess { get; }
+
+        protected internal override bool IsRefundSuccess { get; }
+
+        protected internal override bool IsCancelSuccess { get; }
+
+        protected internal override string[] NotifyVerifyParameter { get; }
+
+        protected internal override async Task<bool> ValidateNotifyAsync()
+        {
+            return await Task.Run(() => { return false; });
+        }
+
+        public override TResponse Execute<TModel, TResponse>(Request<TModel, TResponse> request)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 23 - 0
PaySharp.Core/IMerchant.cs

@@ -0,0 +1,23 @@
+namespace PaySharp.Core
+{
+    /// <summary>
+    /// 商户接口
+    /// </summary>
+    public interface IMerchant
+    {
+        /// <summary>
+        /// 应用ID
+        /// </summary>
+        string AppId { get; set; }
+
+        /// <summary>
+        /// 签名类型
+        /// </summary>
+        string SignType { get; }
+
+        /// <summary>
+        /// 网关回发通知URL
+        /// </summary>
+        string NotifyUrl { get; set; }
+    }
+}

+ 142 - 0
PaySharp.Core/Notify/Notify.cs

@@ -0,0 +1,142 @@
+using System;
+using System.Threading.Tasks;
+using PaySharp.Core.Exceptions;
+using PaySharp.Core.Utils;
+
+namespace PaySharp.Core
+{
+    /// <summary>
+    /// 网关返回的支付通知数据的接受
+    /// </summary>
+    public class Notify
+    {
+        #region 私有字段
+
+        private readonly IGateways _gateways;
+
+        #endregion
+
+        #region 构造函数
+
+        /// <summary>
+        /// 初始化支付通知
+        /// </summary>
+        /// <param name="gateways">用于验证支付网关返回数据的网关列表</param>
+        public Notify(IGateways gateways)
+        {
+            _gateways = gateways;
+        }
+
+        #endregion
+
+        #region 事件
+
+        /// <summary>
+        /// 网关异步返回的支付通知验证成功时触发
+        /// </summary>
+        public event Func<object, PaySucceedEventArgs, bool> PaySucceed;
+
+        /// <summary>
+        /// 网关异步返回的撤销通知验证成功时触发
+        /// </summary>
+        public event Func<object, CancelSucceedEventArgs, bool> CancelSucceed;
+
+        /// <summary>
+        /// 网关异步返回的退款通知验证成功时触发
+        /// </summary>
+        public event Func<object, RefundSucceedEventArgs, bool> RefundSucceed;
+
+        /// <summary>
+        /// 网关异步返回的未知通知时触发
+        /// </summary>
+        public event Func<object, UnKnownNotifyEventArgs, bool> UnknownNotify;
+
+        /// <summary>
+        /// 找不到网关时触发
+        /// </summary>
+        public event Action<object, UnknownGatewayEventArgs> UnknownGateway;
+
+        #endregion
+
+        #region 方法
+
+        private bool OnPaySucceed(PaySucceedEventArgs e) => PaySucceed?.Invoke(this, e) ?? false;
+
+        private bool OnCancelSucceed(CancelSucceedEventArgs e) => CancelSucceed?.Invoke(this, e) ?? false;
+
+        private bool OnRefundSucceed(RefundSucceedEventArgs e) => RefundSucceed?.Invoke(this, e) ?? false;
+
+        private bool OnUnknownNotify(UnKnownNotifyEventArgs e) => UnknownNotify?.Invoke(this, e) ?? false;
+
+        private void OnUnknownGateway(UnknownGatewayEventArgs e) => UnknownGateway?.Invoke(this, e);
+
+        /// <summary>
+        /// 接收并验证网关的支付通知
+        /// </summary>
+        public async Task ReceivedAsync()
+        {
+            var gateway = await NotifyProcess.GetGatewayAsync(_gateways);
+            if (gateway is NullGateway)
+            {
+                OnUnknownGateway(new UnknownGatewayEventArgs(gateway));
+                return;
+            }
+
+            try
+            {
+                if (!await gateway.ValidateNotifyAsync())
+                {
+                    OnUnknownNotify(new UnKnownNotifyEventArgs(gateway)
+                    {
+                        Message = "签名验证失败"
+                    });
+                    gateway.WriteFailureFlag();
+                    return;
+                }
+
+                if (HttpUtil.RequestType == "GET")
+                {
+                    OnPaySucceed(new PaySucceedEventArgs(gateway));
+                    return;
+                }
+
+                var result = false;
+                if (gateway.IsPaySuccess)
+                {
+                    result = OnPaySucceed(new PaySucceedEventArgs(gateway));
+                }
+                else if (gateway.IsRefundSuccess)
+                {
+                    result = OnRefundSucceed(new RefundSucceedEventArgs(gateway));
+                }
+                else if (gateway.IsCancelSuccess)
+                {
+                    result = OnCancelSucceed(new CancelSucceedEventArgs(gateway));
+                }
+                else
+                {
+                    result = OnUnknownNotify(new UnKnownNotifyEventArgs(gateway));
+                }
+
+                if (result)
+                {
+                    gateway.WriteSuccessFlag();
+                }
+                else
+                {
+                    gateway.WriteFailureFlag();
+                }
+            }
+            catch (GatewayException ex)
+            {
+                OnUnknownNotify(new UnKnownNotifyEventArgs(gateway)
+                {
+                    Message = ex.Message
+                });
+                gateway.WriteFailureFlag();
+            }
+        }
+
+        #endregion
+    }
+}

+ 112 - 0
PaySharp.Core/Notify/NotifyProcess.cs

@@ -0,0 +1,112 @@
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using PaySharp.Core.Utils;
+
+namespace PaySharp.Core
+{
+    /// <summary>
+    /// 网关通知的处理类,通过对返回数据的分析识别网关类型
+    /// </summary>
+    internal static class NotifyProcess
+    {
+        /// <summary>
+        /// 是否是Xml格式数据
+        /// </summary>
+        /// <returns></returns>
+        private static bool IsXmlData => HttpUtil.ContentType == "text/xml" || HttpUtil.ContentType == "application/xml";
+
+        /// <summary>
+        /// 是否是GET请求
+        /// </summary>
+        /// <returns></returns>
+        private static bool IsGetRequest => HttpUtil.RequestType == "GET";
+
+        /// <summary>
+        /// 获取网关
+        /// </summary>
+        /// <param name="gateways">网关列表</param>
+        /// <returns></returns>
+        public static async Task<BaseGateway> GetGatewayAsync(IGateways gateways)
+        {
+            var gatewayData = await ReadNotifyDataAsync();
+            BaseGateway gateway = null;
+
+            foreach (var item in gateways.GetList())
+            {
+                if (ExistParameter(item.NotifyVerifyParameter, gatewayData))
+                {
+                    if (item.Merchant.AppId == gatewayData
+                        .GetStringValue(item.NotifyVerifyParameter.FirstOrDefault()))
+                    {
+                        gateway = item;
+                        break;
+                    }
+                }
+            }
+
+            if (gateway is null)
+            {
+                gateway = new NullGateway();
+            }
+
+            gateway.GatewayData = gatewayData;
+            return gateway;
+        }
+
+        /// <summary>
+        /// 网关参数数据项中是否存在指定的所有参数名
+        /// </summary>
+        /// <param name="parmaName">参数名数组</param>
+        /// <param name="gatewayData">网关数据</param>
+        public static bool ExistParameter(string[] parmaName, GatewayData gatewayData)
+        {
+            var compareCount = 0;
+            foreach (var item in parmaName)
+            {
+                if (gatewayData.Exists(item))
+                {
+                    compareCount++;
+                }
+            }
+
+            return compareCount == parmaName.Length;
+        }
+
+        /// <summary>
+        /// 读取网关发回的数据
+        /// </summary>
+        /// <returns></returns>
+        public static async Task<GatewayData> ReadNotifyDataAsync()
+        {
+            var gatewayData = new GatewayData();
+            if (IsGetRequest)
+            {
+                gatewayData.FromUrl(HttpUtil.QueryString);
+            }
+            else
+            {
+                if (IsXmlData)
+                {
+                    var reader = new StreamReader(HttpUtil.Body);
+                    var xmlData = await reader.ReadToEndAsync();
+                    gatewayData.FromXml(xmlData);
+                }
+                else
+                {
+                    try
+                    {
+#if NETCOREAPP3_1
+                        gatewayData.FromForm(HttpUtil.Form);
+#else
+                        gatewayData.FromNameValueCollection(HttpUtil.Form);
+#endif
+                    }
+                    catch { }
+                }
+            }
+
+            return gatewayData;
+        }
+    }
+}

+ 18 - 0
PaySharp.Core/Notify/NotifyType.cs

@@ -0,0 +1,18 @@
+namespace PaySharp.Core
+{
+    /// <summary>
+    /// 通知类型
+    /// </summary>
+    public enum NotifyType
+    {
+        /// <summary>
+        /// 同步
+        /// </summary>
+        Sync,
+
+        /// <summary>
+        /// 异步
+        /// </summary>
+        Async
+    }
+}

+ 21 - 0
PaySharp.Core/PaySharp.Core.csproj

@@ -0,0 +1,21 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>netcoreapp3.1</TargetFrameworks>
+    <Title>PaySharp.Core</Title>
+    <Description>支付核心</Description>
+    <PackageTags>dotnetcore;pay;</PackageTags>
+    <PackageReleaseNotes>
+      支持.net core3.1
+    </PackageReleaseNotes>
+  </PropertyGroup>
+
+  <ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.1'">
+    <FrameworkReference Include="Microsoft.AspNetCore.App" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
+  </ItemGroup>
+
+</Project>

二進制
PaySharp.Core/PaySharp.snk


+ 56 - 0
PaySharp.Core/Request/Request.cs

@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+using PaySharp.Core.Response;
+using PaySharp.Core.Utils;
+
+namespace PaySharp.Core.Request
+{
+    public abstract class Request<TModel, TResponse> where TResponse : IResponse
+    {
+        protected Request()
+        {
+            GatewayData = new GatewayData();
+        }
+
+        protected Request(IComparer<string> comparer)
+        {
+            GatewayData = new GatewayData(comparer);
+        }
+
+        /// <summary>
+        /// 请求地址
+        /// </summary>
+        public string RequestUrl { get; set; }
+
+        /// <summary>
+        /// 异步通知地址
+        /// </summary>
+        public string NotifyUrl { get; set; }
+
+        /// <summary>
+        /// 同步通知地址
+        /// </summary>
+        public string ReturnUrl { get; set; }
+
+        /// <summary>
+        /// 网关数据
+        /// </summary>
+        /// <returns></returns>
+        public GatewayData GatewayData { get; }
+
+        /// <summary>
+        /// 模型
+        /// </summary>
+        /// <returns></returns>
+        public TModel Model { get; private set; }
+
+        /// <summary>
+        /// 添加网关数据
+        /// </summary>
+        /// <param name="model">模型</param>
+        public virtual void AddGatewayData(TModel model)
+        {
+            Model = model;
+            ValidateUtil.Validate(model, null);
+        }
+    }
+}

+ 7 - 0
PaySharp.Core/Response/IResponse.cs

@@ -0,0 +1,7 @@
+namespace PaySharp.Core.Response
+{
+    public interface IResponse
+    {
+        string Raw { get; set; }
+    }
+}

+ 45 - 0
PaySharp.Core/ServiceCollectionExtensions.cs

@@ -0,0 +1,45 @@
+#if NETCOREAPP3_1
+using System;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using PaySharp.Core;
+using PaySharp.Core.Utils;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+    public static class ServiceCollectionExtensions
+    {
+        /// <summary>
+        /// 添加PaySharp
+        /// </summary>
+        /// <param name="services"></param>
+        /// <param name="setupAction"></param>
+        public static void AddPaySharp(this IServiceCollection services, Action<IGateways> setupAction)
+        {
+            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
+
+            services.AddScoped<IGateways>(a =>
+            {
+                var gateways = new Gateways();
+                setupAction(gateways);
+
+                return gateways;
+            });
+        }
+
+        /// <summary>
+        /// 使用PaySharp
+        /// </summary>
+        /// <param name="app"></param>
+        /// <returns></returns>
+        public static IApplicationBuilder UsePaySharp(this IApplicationBuilder app)
+        {
+            var httpContextAccessor = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
+            HttpUtil.Configure(httpContextAccessor);
+
+            return app;
+        }
+    }
+}
+
+#endif

+ 741 - 0
PaySharp.Core/Utils/EncryptUtil.cs

@@ -0,0 +1,741 @@
+using System;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+using PaySharp.Core.Exceptions;
+
+namespace PaySharp.Core.Utils
+{
+    /// <summary>
+    /// 加密工具类
+    /// </summary>
+    public static class EncryptUtil
+    {
+        #region 私有字段
+
+        /// <summary>
+        /// 默认编码
+        /// </summary>
+        private static readonly string _defaultCharset = "UTF-8";
+
+        #endregion
+
+        #region MD5加密
+
+        /// <summary>
+        /// MD5加密
+        /// </summary>
+        /// <param name="data">数据</param>
+        public static string MD5(string data)
+        {
+            return MD5(data, Encoding.UTF8);
+        }
+
+        /// <summary>
+        /// MD5加密
+        /// </summary>
+        /// <param name="data">数据</param>
+        /// <param name="encoding">编码</param>
+        /// <returns></returns>
+        public static string MD5(string data, Encoding encoding)
+        {
+            var md5 = System.Security.Cryptography.MD5.Create();
+            var dataByte = md5.ComputeHash(encoding.GetBytes(data));
+
+            return BitConverter.ToString(dataByte).Replace("-", "");
+        }
+
+        #endregion
+
+        #region RSA加密
+
+        /// <summary>
+        /// RSA加密
+        /// </summary>
+        /// <param name="data">数据</param>
+        /// <param name="privateKey">私钥</param>
+        /// <param name="signType">签名类型</param>
+        /// <returns></returns>
+        public static string RSA(string data, string privateKey, string signType)
+        {
+            return RSA(data, privateKey, _defaultCharset, signType, false);
+        }
+
+        private static string RSA(string data, string privateKeyPem, string charset, string signType, bool keyFromFile)
+        {
+
+            byte[] signatureBytes = null;
+            try
+            {
+                RSA rsaCsp = null;
+                if (keyFromFile)
+                {
+                    //文件读取
+                    rsaCsp = LoadCertificateFile(privateKeyPem, signType);
+                }
+                else
+                {
+                    //字符串获取
+                    rsaCsp = LoadCertificateString(privateKeyPem, signType);
+                }
+
+                byte[] dataBytes = null;
+
+                if (string.IsNullOrEmpty(charset))
+                {
+                    dataBytes = Encoding.UTF8.GetBytes(data);
+                }
+                else
+                {
+                    dataBytes = Encoding.GetEncoding(charset).GetBytes(data);
+                }
+
+                if (null == rsaCsp)
+                {
+                    throw new GatewayException("您使用的私钥格式错误,请检查RSA私钥配置" + ",charset = " + charset);
+                }
+
+                if ("RSA2".Equals(signType))
+                {
+#if NETCOREAPP3_1
+                    signatureBytes = rsaCsp.SignData(dataBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
+#else
+                    signatureBytes = ((RSACryptoServiceProvider)rsaCsp).SignData(dataBytes, "SHA256");
+#endif
+                }
+                else
+                {
+#if NETCOREAPP3_1
+                    signatureBytes = rsaCsp.SignData(dataBytes, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
+#else
+                    signatureBytes = ((RSACryptoServiceProvider)rsaCsp).SignData(dataBytes, "SHA1");
+#endif
+                }
+            }
+            catch
+            {
+                throw new GatewayException("您使用的私钥格式错误,请检查RSA私钥配置" + ",charset = " + charset);
+            }
+
+            return Convert.ToBase64String(signatureBytes);
+        }
+
+        /// <summary>
+        /// RSA2验签
+        /// </summary>
+        /// <param name="data">数据</param>
+        /// <param name="sign">签名</param>
+        /// <param name="publicKey">公钥</param>
+        /// <param name="signType">签名类型</param>
+        /// <returns></returns>
+        public static bool RSAVerifyData(string data, string sign, string publicKey, string signType)
+        {
+            return RSAVerifyData(data, sign, publicKey, _defaultCharset, signType, false);
+        }
+
+        private static bool RSAVerifyData(string signContent, string sign, string publicKeyPem, string charset, string signType, bool keyFromFile)
+        {
+            try
+            {
+                var sPublicKeyPEM = publicKeyPem;
+
+                if (keyFromFile)
+                {
+                    sPublicKeyPEM = File.ReadAllText(publicKeyPem);
+                }
+                var rsa = CreateRsaProviderFromPublicKey(sPublicKeyPEM, signType);
+                var bVerifyResultOriginal = false;
+
+                if ("RSA2".Equals(signType))
+                {
+#if NETCOREAPP3_1
+                    bVerifyResultOriginal = rsa.VerifyData(Encoding.GetEncoding(charset).GetBytes(signContent),
+                       Convert.FromBase64String(sign), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
+#else
+                    bVerifyResultOriginal = ((RSACryptoServiceProvider)rsa).VerifyData(
+                        Encoding.GetEncoding(charset).GetBytes(signContent), "SHA256", Convert.FromBase64String(sign));
+#endif
+                }
+                else
+                {
+#if NETCOREAPP3_1
+                    bVerifyResultOriginal = rsa.VerifyData(Encoding.GetEncoding(charset).GetBytes(signContent),
+                       Convert.FromBase64String(sign), HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
+#else
+                    bVerifyResultOriginal = ((RSACryptoServiceProvider)rsa).VerifyData(
+                        Encoding.GetEncoding(charset).GetBytes(signContent), "SHA1", Convert.FromBase64String(sign));
+#endif
+                }
+
+                return bVerifyResultOriginal;
+            }
+            catch
+            {
+                return false;
+            }
+
+        }
+
+        private static RSA CreateRsaProviderFromPublicKey(string publicKeyString, string signType)
+        {
+            byte[] seqOid = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
+            var seq = new byte[15];
+
+            var x509Key = Convert.FromBase64String(publicKeyString);
+            using var mem = new MemoryStream(x509Key);
+            using var binr = new BinaryReader(mem);
+            byte bt = 0;
+            ushort twobytes = 0;
+
+            twobytes = binr.ReadUInt16();
+            if (twobytes == 0x8130)
+            {
+                binr.ReadByte();
+            }
+            else if (twobytes == 0x8230)
+            {
+                binr.ReadInt16();
+            }
+            else
+            {
+                return null;
+            }
+
+            seq = binr.ReadBytes(15);
+            if (!CompareBytearrays(seq, seqOid))
+            {
+                return null;
+            }
+
+            twobytes = binr.ReadUInt16();
+            if (twobytes == 0x8103)
+            {
+                binr.ReadByte();
+            }
+            else if (twobytes == 0x8203)
+            {
+                binr.ReadInt16();
+            }
+            else
+            {
+                return null;
+            }
+
+            bt = binr.ReadByte();
+            if (bt != 0x00)
+            {
+                return null;
+            }
+
+            twobytes = binr.ReadUInt16();
+            if (twobytes == 0x8130)
+            {
+                binr.ReadByte();
+            }
+            else if (twobytes == 0x8230)
+            {
+                binr.ReadInt16();
+            }
+            else
+            {
+                return null;
+            }
+
+            twobytes = binr.ReadUInt16();
+            byte lowbyte = 0x00;
+            byte highbyte = 0x00;
+
+            if (twobytes == 0x8102)
+            {
+                lowbyte = binr.ReadByte();
+            }
+            else if (twobytes == 0x8202)
+            {
+                highbyte = binr.ReadByte();
+                lowbyte = binr.ReadByte();
+            }
+            else
+            {
+                return null;
+            }
+            byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
+            var modsize = BitConverter.ToInt32(modint, 0);
+
+            var firstbyte = binr.PeekChar();
+            if (firstbyte == 0x00)
+            {
+                binr.ReadByte();
+                modsize -= 1;
+            }
+
+            var modulus = binr.ReadBytes(modsize);
+
+            if (binr.ReadByte() != 0x02)
+            {
+                return null;
+            }
+            int expbytes = binr.ReadByte();
+            var exponent = binr.ReadBytes(expbytes);
+
+            var rsa = System.Security.Cryptography.RSA.Create();
+            rsa.KeySize = signType == "RSA" ? 1024 : 2048;
+            var rsaKeyInfo = new RSAParameters
+            {
+                Modulus = modulus,
+                Exponent = exponent
+            };
+            rsa.ImportParameters(rsaKeyInfo);
+
+            return rsa;
+        }
+
+        private static bool CompareBytearrays(byte[] a, byte[] b)
+        {
+            if (a.Length != b.Length)
+            {
+                return false;
+            }
+            var i = 0;
+            foreach (var c in a)
+            {
+                if (c != b[i])
+                {
+                    return false;
+                }
+                i++;
+            }
+            return true;
+        }
+
+        private static RSA LoadCertificateFile(string filename, string signType)
+        {
+            using var fs = File.OpenRead(filename);
+            var data = new byte[fs.Length];
+            byte[] res = null;
+            fs.Read(data, 0, data.Length);
+            if (data[0] != 0x30)
+            {
+                res = GetPem("RSA PRIVATE KEY", data);
+            }
+            try
+            {
+                return DecodeRSAPrivateKey(res, signType);
+            }
+            catch
+            {
+                return null;
+            }
+        }
+
+        private static RSA LoadCertificateString(string strKey, string signType)
+        {
+            var data = Convert.FromBase64String(strKey);
+            try
+            {
+                return DecodeRSAPrivateKey(data, signType);
+            }
+            catch
+            {
+                return null;
+            }
+        }
+
+        private static RSA DecodeRSAPrivateKey(byte[] privkey, string signType)
+        {
+            byte[] modulus, e, d, p, q, dP, dQ, iQ;
+            var mem = new MemoryStream(privkey);
+            var binr = new BinaryReader(mem);
+            try
+            {
+                var twobytes = binr.ReadUInt16();
+                if (twobytes == 0x8130)
+                {
+                    binr.ReadByte();
+                }
+                else if (twobytes == 0x8230)
+                {
+                    binr.ReadInt16();
+                }
+                else
+                {
+                    return null;
+                }
+
+                twobytes = binr.ReadUInt16();
+                if (twobytes != 0x0102)
+                {
+                    return null;
+                }
+
+                var bt = binr.ReadByte();
+                if (bt != 0x00)
+                {
+                    return null;
+                }
+
+                var elems = GetIntegerSize(binr);
+                modulus = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                e = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                d = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                p = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                q = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                dP = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                dQ = binr.ReadBytes(elems);
+
+                elems = GetIntegerSize(binr);
+                iQ = binr.ReadBytes(elems);
+
+                var bitLen = 1024;
+                if ("RSA2".Equals(signType))
+                {
+                    bitLen = 2048;
+                }
+
+                var rsa = System.Security.Cryptography.RSA.Create();
+                rsa.KeySize = bitLen;
+                var rsaParams = new RSAParameters
+                {
+                    Modulus = modulus,
+                    Exponent = e,
+                    D = d,
+                    P = p,
+                    Q = q,
+                    DP = dP,
+                    DQ = dQ,
+                    InverseQ = iQ
+                };
+                rsa.ImportParameters(rsaParams);
+                return rsa;
+            }
+            catch
+            {
+                return null;
+            }
+            finally
+            {
+                binr.Close();
+            }
+        }
+
+        private static byte[] GetPem(string type, byte[] data)
+        {
+            var pem = Encoding.UTF8.GetString(data);
+            var header = string.Format("-----BEGIN {0}-----\\n", type);
+            var footer = string.Format("-----END {0}-----", type);
+            var start = pem.IndexOf(header) + header.Length;
+            var end = pem.IndexOf(footer, start);
+            var base64 = pem.Substring(start, end - start);
+
+            return Convert.FromBase64String(base64);
+        }
+
+        #endregion
+
+        #region SHA256加密
+
+        /// <summary>
+        /// SHA256加密
+        /// </summary>
+        /// <param name="data">数据</param>
+        /// <returns></returns>
+        public static string SHA256(string data)
+        {
+            var byteData = Encoding.UTF8.GetBytes(data);
+            var sha256 = new SHA256Managed();
+            var result = sha256.ComputeHash(byteData);
+            return BitConverter.ToString(result).Replace("-", "").ToLower();
+        }
+
+        #endregion
+
+        #region HMACSHA1加密
+
+        /// <summary>
+        /// HMACSHA1加密
+        /// </summary>
+        /// <param name="data">数据</param>
+        /// <param name="key">密钥</param>
+        /// <returns></returns>
+        public static string HMACSHA1(string data, string key)
+        {
+            var byteData = Encoding.UTF8.GetBytes(data);
+            var byteKey = Encoding.UTF8.GetBytes(key);
+            var hmacsha1 = new HMACSHA1(byteKey);
+            var result = hmacsha1.ComputeHash(byteData);
+            return Convert.ToBase64String(result);
+        }
+
+        #endregion
+
+        #region HMACSHA256加密
+
+        /// <summary>
+        /// HMACSHA256加密
+        /// </summary>
+        /// <param name="data">数据</param>
+        /// <param name="key">密钥</param>
+        /// <returns></returns>
+        public static string HMACSHA256(string data, string key)
+        {
+            var byteData = Encoding.UTF8.GetBytes(data);
+            var byteKey = Encoding.UTF8.GetBytes(key);
+            var hmacsha256 = new HMACSHA256(byteKey);
+            var result = hmacsha256.ComputeHash(byteData);
+            return BitConverter.ToString(result).Replace("-", "").ToLower();
+        }
+
+        #endregion
+
+        #region AES解密
+
+        /// <summary>
+        /// AES解密
+        /// </summary>
+        /// <param name="data">待解密数据</param>
+        /// <param name="key">密钥</param>
+        /// <returns></returns>
+        public static string AESDecrypt(string data, string key)
+        {
+            var keyArray = Encoding.UTF8.GetBytes(key);
+            var toEncryptArray = Convert.FromBase64String(data);
+            var rDel = new RijndaelManaged
+            {
+                Key = keyArray,
+                Mode = CipherMode.ECB,
+                Padding = PaddingMode.PKCS7
+            };
+
+            var cTransform = rDel.CreateDecryptor();
+            var resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
+            return Encoding.UTF8.GetString(resultArray);
+        }
+
+        #endregion
+
+
+        #region from alipay signer
+
+        /// <summary>
+        /// 验证签名
+        /// </summary>
+        /// <param name="content">待验签的内容</param>
+        /// <param name="sign">签名值的Base64串</param>
+        /// <param name="publicKeyPem">支付宝公钥</param>
+        /// <returns>true:验证成功;false:验证失败</returns>
+        public static bool Verify(string content, string sign, string publicKeyPem)
+        {
+            try
+            {
+                using (RSACryptoServiceProvider rsaService = new RSACryptoServiceProvider())
+                {
+                    rsaService.PersistKeyInCsp = false;
+                    rsaService.ImportParameters(ConvertFromPemPublicKey(publicKeyPem));
+                    return rsaService.VerifyData(Encoding.UTF8.GetBytes(content),
+                        "SHA256", Convert.FromBase64String(sign));
+                }
+            }
+            catch (Exception e)
+            {
+                string errorMessage = "验签遭遇异常,content=" + content + " sign=" + sign +
+                   " publicKey=" + publicKeyPem + " reason=" + e.Message;
+                throw new Exception(errorMessage, e);
+            }
+
+        }
+
+        /// <summary>
+        /// SHA256WithRSA 计算签名
+        /// </summary>
+        /// <param name="content">数据</param>
+        /// <param name="privateKeyPem">私钥</param>
+        /// <returns></returns>
+        public static string Sign(string content, string privateKeyPem)
+        {
+            try
+            {
+                using (RSACryptoServiceProvider rsaService = BuildRSAServiceProvider(Convert.FromBase64String(privateKeyPem)))
+                {
+                    byte[] data = Encoding.UTF8.GetBytes(content);
+                    byte[] sign = rsaService.SignData(data, "SHA256");
+                    return Convert.ToBase64String(sign);
+                }
+            }
+            catch (Exception e)
+            {
+                string errorMessage = "签名遭遇异常,content=" + content + " privateKeySize=" + privateKeyPem.Length + " reason=" + e.Message;
+                throw new Exception(errorMessage, e);
+            }
+        }
+
+
+        private static RSAParameters ConvertFromPemPublicKey(string pemPublickKey)
+        {
+            if (string.IsNullOrEmpty(pemPublickKey))
+            {
+                throw new Exception("PEM格式公钥不可为空。");
+            }
+
+            //移除干扰文本
+            pemPublickKey = pemPublickKey.Replace("-----BEGIN PUBLIC KEY-----", "")
+                .Replace("-----END PUBLIC KEY-----", "").Replace("\n", "").Replace("\r", "");
+
+            byte[] keyData = Convert.FromBase64String(pemPublickKey);
+            bool keySize1024 = (keyData.Length == 162);
+            bool keySize2048 = (keyData.Length == 294);
+            if (!(keySize1024 || keySize2048))
+            {
+                throw new Exception("公钥长度只支持1024和2048。");
+            }
+            byte[] pemModulus = (keySize1024 ? new byte[128] : new byte[256]);
+            byte[] pemPublicExponent = new byte[3];
+            Array.Copy(keyData, (keySize1024 ? 29 : 33), pemModulus, 0, (keySize1024 ? 128 : 256));
+            Array.Copy(keyData, (keySize1024 ? 159 : 291), pemPublicExponent, 0, 3);
+            RSAParameters para = new RSAParameters
+            {
+                Modulus = pemModulus,
+                Exponent = pemPublicExponent
+            };
+            return para;
+        }
+
+        private static RSACryptoServiceProvider BuildRSAServiceProvider(byte[] privateKey)
+        {
+            byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
+            byte bt = 0;
+            ushort twobytes = 0;
+            int elems = 0;
+
+            //set up stream to decode the asn.1 encoded RSA private key
+            //wrap Memory Stream with BinaryReader for easy reading
+            using (BinaryReader binaryReader = new BinaryReader(new MemoryStream(privateKey)))
+            {
+                twobytes = binaryReader.ReadUInt16();
+                //data read as little endian order (actual data order for Sequence is 30 81)
+                if (twobytes == 0x8130)
+                {
+                    //advance 1 byte
+                    binaryReader.ReadByte();
+                }
+                else if (twobytes == 0x8230)
+                {
+                    //advance 2 bytes
+                    binaryReader.ReadInt16();
+                }
+                else
+                {
+                    return null;
+                }
+
+                twobytes = binaryReader.ReadUInt16();
+                //version number
+                if (twobytes != 0x0102)
+                {
+                    return null;
+                }
+                bt = binaryReader.ReadByte();
+                if (bt != 0x00)
+                {
+                    return null;
+                }
+
+                //all private key components are Integer sequences
+                elems = GetIntegerSize(binaryReader);
+                MODULUS = binaryReader.ReadBytes(elems);
+
+                elems = GetIntegerSize(binaryReader);
+                E = binaryReader.ReadBytes(elems);
+
+                elems = GetIntegerSize(binaryReader);
+                D = binaryReader.ReadBytes(elems);
+
+                elems = GetIntegerSize(binaryReader);
+                P = binaryReader.ReadBytes(elems);
+
+                elems = GetIntegerSize(binaryReader);
+                Q = binaryReader.ReadBytes(elems);
+
+                elems = GetIntegerSize(binaryReader);
+                DP = binaryReader.ReadBytes(elems);
+
+                elems = GetIntegerSize(binaryReader);
+                DQ = binaryReader.ReadBytes(elems);
+
+                elems = GetIntegerSize(binaryReader);
+                IQ = binaryReader.ReadBytes(elems);
+
+                //create RSACryptoServiceProvider instance and initialize with public key
+                RSACryptoServiceProvider rsaService = new RSACryptoServiceProvider();
+                RSAParameters rsaParams = new RSAParameters
+                {
+                    Modulus = MODULUS,
+                    Exponent = E,
+                    D = D,
+                    P = P,
+                    Q = Q,
+                    DP = DP,
+                    DQ = DQ,
+                    InverseQ = IQ
+                };
+                rsaService.ImportParameters(rsaParams);
+                return rsaService;
+            }
+        }
+
+        private static int GetIntegerSize(BinaryReader binaryReader)
+        {
+            byte bt = 0;
+            byte lowbyte = 0x00;
+            byte highbyte = 0x00;
+            int count = 0;
+
+            bt = binaryReader.ReadByte();
+
+            //expect integer
+            if (bt != 0x02)
+            {
+                return 0;
+            }
+            bt = binaryReader.ReadByte();
+
+            if (bt == 0x81)
+            {
+                //data size in next byte
+                count = binaryReader.ReadByte();
+            }
+            else if (bt == 0x82)
+            {
+                //data size in next 2 bytes
+                highbyte = binaryReader.ReadByte();
+                lowbyte = binaryReader.ReadByte();
+                byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
+                count = BitConverter.ToInt32(modint, 0);
+            }
+            else
+            {
+                //we already have the data size
+                count = bt;
+            }
+            while (binaryReader.ReadByte() == 0x00)
+            {   //remove high order zeros in data
+                count -= 1;
+            }
+            //last ReadByte wasn't a removed zero, so back up a byte
+            binaryReader.BaseStream.Seek(-1, SeekOrigin.Current);
+            return count;
+        }
+
+        #endregion
+    }
+}

+ 382 - 0
PaySharp.Core/Utils/HttpUtil.cs

@@ -0,0 +1,382 @@
+#if NETCOREAPP3_1
+using Microsoft.AspNetCore.Http;
+#else
+using System.Collections.Specialized;
+using System.Web;
+#endif
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PaySharp.Core.Utils
+{
+    /// <summary>
+    /// Http工具类
+    /// </summary>
+    public static class HttpUtil
+    {
+        #region 属性
+
+#if NETCOREAPP3_1
+
+        private static IHttpContextAccessor _httpContextAccessor;
+
+        /// <summary>
+        /// 当前上下文
+        /// </summary>
+        public static HttpContext Current => _httpContextAccessor.HttpContext;
+
+        /// <summary>
+        /// 本地IP
+        /// </summary>
+        public static string LocalIpAddress
+        {
+            get
+            {
+                try
+                {
+                    var ipAddress = Current.Connection.LocalIpAddress;
+                    return IPAddress.IsLoopback(ipAddress) ?
+                           IPAddress.Loopback.ToString() :
+                           ipAddress.MapToIPv4().ToString();
+                }
+                catch
+                {
+                    return IPAddress.Loopback.ToString();
+                }
+            }
+        }
+
+        /// <summary>
+        /// 客户端IP
+        /// </summary>
+        public static string RemoteIpAddress
+        {
+            get
+            {
+                try
+                {
+                    var ipAddress = Current.Connection.RemoteIpAddress;
+                    return IPAddress.IsLoopback(ipAddress) ?
+                           IPAddress.Loopback.ToString() :
+                           ipAddress.MapToIPv4().ToString();
+                }
+                catch
+                {
+                    return IPAddress.Loopback.ToString();
+                }
+            }
+        }
+
+        /// <summary>
+        /// 请求类型
+        /// </summary>
+        public static string RequestType => Current.Request.Method;
+
+        /// <summary>
+        /// 表单
+        /// </summary>
+        public static IFormCollection Form => Current.Request.Form;
+
+        /// <summary>
+        /// 请求体
+        /// </summary>
+        public static Stream Body
+        {
+            get
+            {
+                var body = Current.Request.Body;
+                try
+                {
+                    if (body.CanSeek)
+                    {
+                        body.Position = 0;
+                    }
+                }
+                catch
+                { }
+
+                return body;
+            }
+        }
+
+        #region 构造函数
+
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        static HttpUtil()
+        {
+            ServicePointManager.DefaultConnectionLimit = 200;
+        }
+
+        /// <summary>
+        /// 配置
+        /// </summary>
+        /// <param name="httpContextAccessor"></param>
+        internal static void Configure(IHttpContextAccessor httpContextAccessor)
+        {
+            _httpContextAccessor = httpContextAccessor;
+        }
+
+        #endregion
+
+#else
+
+        public static HttpContext Current => HttpContext.Current;
+
+        /// <summary>
+        /// 本地IP
+        /// </summary>
+        public static string LocalIpAddress
+        {
+            get
+            {
+                try
+                {
+                    var ip = Current.Request.UserHostAddress;
+                    var ipAddress = IPAddress.Parse(ip.Split(':')[0]);
+                    return IPAddress.IsLoopback(ipAddress) ?
+                           IPAddress.Loopback.ToString() :
+                           ipAddress.MapToIPv4().ToString();
+                }
+                catch
+                {
+                    return IPAddress.Loopback.ToString();
+                }
+            }
+        }
+
+        /// <summary>
+        /// 客户端IP
+        /// </summary>
+        public static string RemoteIpAddress
+        {
+            get
+            {
+                try
+                {
+                    var ip = Current.Request.ServerVariables["HTTP_X_FORWARDED_FOR"] ??
+                             Current.Request.ServerVariables["REMOTE_ADDR"];
+                    var ipAddress = IPAddress.Parse(ip.Split(':')[0]);
+                    return IPAddress.IsLoopback(ipAddress) ?
+                           IPAddress.Loopback.ToString() :
+                           ipAddress.MapToIPv4().ToString();
+                }
+                catch
+                {
+                    return IPAddress.Loopback.ToString();
+                }
+            }
+        }
+
+        /// <summary>
+        /// 请求类型
+        /// </summary>
+        public static string RequestType => Current.Request.HttpMethod;
+
+        /// <summary>
+        /// 表单
+        /// </summary>
+        public static NameValueCollection Form => Current.Request.Form;
+
+        /// <summary>
+        /// 请求体
+        /// </summary>
+        public static Stream Body
+        {
+            get
+            {
+                var inputStream = Current.Request.InputStream;
+                try
+                {
+                    if (inputStream.CanSeek)
+                    {
+                        inputStream.Position = 0;
+                    }
+                }
+                catch { }
+                return inputStream;
+            }
+        }
+
+#endif
+
+        /// <summary>
+        /// 用户代理
+        /// </summary>
+        public static string UserAgent => Current.Request.Headers["User-Agent"];
+
+        /// <summary>
+        /// 内容类型
+        /// </summary>
+        public static string ContentType => Current.Request.ContentType;
+
+        /// <summary>
+        /// 参数
+        /// </summary>
+        public static string QueryString => Current.Request.QueryString.ToString();
+
+        #endregion
+
+        #region 方法
+
+        /// <summary>
+        /// 跳转到指定链接
+        /// </summary>
+        /// <param name="url">链接</param>
+        public static void Redirect(string url)
+        {
+            Current.Response.Redirect(url);
+        }
+
+        /// <summary>
+        /// 输出内容
+        /// </summary>
+        /// <param name="text">内容</param>
+        public static void Write(string text)
+        {
+            Current.Response.ContentType = "text/plain;charset=utf-8";
+
+#if NETCOREAPP3_1
+            Task.Run(async () =>
+            {
+                await Current.Response.WriteAsync(text);
+            })
+            .GetAwaiter()
+            .GetResult();
+#else
+            Current.Response.Write(text);
+            Current.Response.End();
+#endif
+
+        }
+
+        /// <summary>
+        /// 输出文件
+        /// </summary>
+        /// <param name="stream">文件流</param>
+        public static void Write(FileStream stream)
+        {
+            var size = stream.Length;
+            var buffer = new byte[size];
+            stream.Read(buffer, 0, (int)size);
+            stream.Dispose();
+            File.Delete(stream.Name);
+
+            Current.Response.ContentType = "application/octet-stream";
+            Current.Response.Headers.Add("Content-Disposition", "attachment;filename=" + WebUtility.UrlEncode(Path.GetFileName(stream.Name)));
+            Current.Response.Headers.Add("Content-Length", size.ToString());
+
+#if NETCOREAPP3_1
+            Task.Run(async () =>
+            {
+                await Current.Response.Body.WriteAsync(buffer, 0, (int)size);
+            })
+            .GetAwaiter()
+            .GetResult();
+            Current.Response.Body.Close();
+#else
+            Current.Response.BinaryWrite(buffer);
+            Current.Response.End();
+            Current.Response.Close();
+#endif
+        }
+
+        /// <summary>
+        /// Get请求
+        /// </summary>
+        /// <param name="url">url</param>
+        /// <returns></returns>
+        public static string Get(string url)
+        {
+            var request = (HttpWebRequest)WebRequest.Create(url);
+            request.Method = "GET";
+            request.ContentType = "application/x-www-form-urlencoded;charset=utf-8";
+
+            using var response = request.GetResponse();
+            using var reader = new StreamReader(response.GetResponseStream());
+            return reader.ReadToEnd().Trim();
+        }
+
+        /// <summary>
+        /// 异步Post请求
+        /// </summary>
+        /// <param name="url">url</param>
+        /// <returns></returns>
+        public static async Task<string> GetAsync(string url)
+        {
+            return await Task.Run(() => Get(url));
+        }
+
+        /// <summary>
+        /// Post请求
+        /// </summary>
+        /// <param name="url">url</param>
+        /// <param name="data">数据</param>
+        /// <param name="cert">证书</param>
+        /// <returns></returns>
+        public static string Post(string url, string data, X509Certificate2 cert = null)
+        {
+            if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase))
+            {
+                ServicePointManager.ServerCertificateValidationCallback =
+                        new RemoteCertificateValidationCallback(CheckValidationResult);
+            }
+
+            var dataByte = Encoding.UTF8.GetBytes(data);
+            var request = (HttpWebRequest)WebRequest.Create(url);
+            request.Method = "POST";
+            request.ContentType = "application/x-www-form-urlencoded;charset=utf-8";
+            request.ContentLength = dataByte.Length;
+            request.UserAgent = "PaySharp";
+
+            if (cert != null)
+            {
+                request.ClientCertificates.Add(cert);
+            }
+
+            using (var outStream = request.GetRequestStream())
+            {
+                outStream.Write(dataByte, 0, dataByte.Length);
+            }
+
+            using var response = (HttpWebResponse)request.GetResponse();
+            using var reader = new StreamReader(response.GetResponseStream());
+            return reader.ReadToEnd().Trim();
+        }
+
+        private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
+        {
+            return true;
+        }
+
+        /// <summary>
+        /// 下载
+        /// </summary>
+        /// <param name="url">url</param>
+        /// <returns></returns>
+        public static byte[] Download(string url)
+        {
+            using var webClient = new WebClient();
+            return webClient.DownloadData(url);
+        }
+
+        /// <summary>
+        /// 异步下载
+        /// </summary>
+        /// <param name="url">url</param>
+        /// <returns></returns>
+        public static async Task<byte[]> DownloadAsync(string url)
+        {
+            using var webClient = new WebClient();
+            return await webClient.DownloadDataTaskAsync(url);
+        }
+
+        #endregion
+    }
+}

+ 145 - 0
PaySharp.Core/Utils/StringUtil.cs

@@ -0,0 +1,145 @@
+using System.Text;
+
+namespace PaySharp.Core.Utils
+{
+    public enum StringCase
+    {
+        /// <summary>
+        /// 蛇形策略
+        /// </summary>
+        Snake,
+
+        /// <summary>
+        /// 骆驼策略
+        /// </summary>
+        Camel,
+
+        /// <summary>
+        /// 小写策略
+        /// </summary>
+        Lower,
+
+        /// <summary>
+        /// 不执行策略
+        /// </summary>
+        None
+    }
+
+    internal enum SnakeCaseState
+    {
+        Start,
+        Lower,
+        Upper,
+        NewWord
+    }
+
+    public static class StringUtil
+    {
+        /// <summary>
+        /// 将字符串转换为蛇形策略
+        /// </summary>
+        /// <param name="s">字符串</param>
+        /// <returns></returns>
+        public static string ToSnakeCase(this string s)
+        {
+            if (string.IsNullOrEmpty(s))
+            {
+                return s;
+            }
+
+            var sb = new StringBuilder();
+            var state = SnakeCaseState.Start;
+
+            for (var i = 0; i < s.Length; i++)
+            {
+                if (s[i] == ' ')
+                {
+                    if (state != SnakeCaseState.Start)
+                    {
+                        state = SnakeCaseState.NewWord;
+                    }
+                }
+                else if (char.IsUpper(s[i]))
+                {
+                    switch (state)
+                    {
+                        case SnakeCaseState.Upper:
+                            var hasNext = i + 1 < s.Length;
+                            if (i > 0 && hasNext)
+                            {
+                                var nextChar = s[i + 1];
+                                if (!char.IsUpper(nextChar) && nextChar != '_')
+                                {
+                                    sb.Append('_');
+                                }
+                            }
+                            break;
+                        case SnakeCaseState.Lower:
+                        case SnakeCaseState.NewWord:
+                            sb.Append('_');
+                            break;
+                    }
+
+                    sb.Append(char.ToLowerInvariant(s[i]));
+
+                    state = SnakeCaseState.Upper;
+                }
+                else if (s[i] == '_')
+                {
+                    sb.Append('_');
+                    state = SnakeCaseState.Start;
+                }
+                else
+                {
+                    if (state == SnakeCaseState.NewWord)
+                    {
+                        sb.Append('_');
+                    }
+
+                    sb.Append(s[i]);
+                    state = SnakeCaseState.Lower;
+                }
+            }
+
+            return sb.ToString();
+        }
+
+        /// <summary>
+        /// 将字符串转换为骆驼策略
+        /// </summary>
+        /// <param name="s">字符串</param>
+        /// <returns></returns>
+        public static string ToCamelCase(this string s)
+        {
+            if (string.IsNullOrEmpty(s) || !char.IsUpper(s[0]))
+            {
+                return s;
+            }
+
+            var chars = s.ToCharArray();
+
+            for (var i = 0; i < chars.Length; i++)
+            {
+                if (i == 1 && !char.IsUpper(chars[i]))
+                {
+                    break;
+                }
+
+                var hasNext = i + 1 < chars.Length;
+                if (i > 0 && hasNext && !char.IsUpper(chars[i + 1]))
+                {
+                    if (char.IsSeparator(chars[i + 1]))
+                    {
+                        chars[i] = char.ToLowerInvariant(chars[i]);
+                    }
+
+                    break;
+                }
+
+                chars[i] = char.ToLowerInvariant(chars[i]);
+            }
+
+            return new string(chars);
+        }
+    }
+}

+ 47 - 0
PaySharp.Core/Utils/Util.cs

@@ -0,0 +1,47 @@
+using System;
+using Newtonsoft.Json;
+
+namespace PaySharp.Core.Utils
+{
+    /// <summary>
+    /// 工具类
+    /// </summary>
+    public static class Util
+    {
+        #region 方法
+
+        /// <summary>
+        /// 序列化对象
+        /// </summary>
+        /// <param name="obj">对象</param>
+        /// <returns></returns>
+        public static string SerializeObject(object obj)
+        {
+            return JsonConvert.SerializeObject(obj, new JsonSerializerSettings()
+            {
+                DefaultValueHandling = DefaultValueHandling.Ignore
+            });
+        }
+
+        /// <summary>
+        /// 生成随机字符串
+        /// </summary>
+        /// <returns></returns>
+        public static string GenerateNonceStr()
+        {
+            return Guid.NewGuid().ToString("N");
+        }
+
+        /// <summary>
+        /// 将时间转换为时间戳
+        /// </summary>
+        /// <param name="time">时间</param>
+        /// <returns></returns>
+        public static int ToTimeStamp(this DateTime time)
+        {
+            return (int)((time.ToUniversalTime().Ticks / 10000000) - 62135596800);
+        }
+
+        #endregion
+    }
+}

+ 29 - 0
PaySharp.Core/Utils/ValidateUtil.cs

@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+
+namespace PaySharp.Core.Utils
+{
+    /// <summary>
+    /// 验证工具类
+    /// </summary>
+    internal static class ValidateUtil
+    {
+        /// <summary>
+        /// 验证
+        /// </summary>
+        /// <param name="obj">验证目标</param>
+        /// <param name="data">上下文数据</param>
+        public static void Validate(object obj, Dictionary<object, object> data)
+        {
+            var validationContext = new ValidationContext(obj, data);
+            var results = new List<ValidationResult>();
+            var isValid = Validator.TryValidateObject(obj, validationContext, results, true);
+
+            if (!isValid)
+            {
+                throw new ArgumentNullException(results[0].ErrorMessage);
+            }
+        }
+    }
+}

+ 74 - 0
PaySharp.Wechatpay/ConvertUtil.cs

@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using PaySharp.Core;
+using PaySharp.Core.Utils;
+
+namespace PaySharp.Wechatpay
+{
+    internal static class ConvertUtil
+    {
+        public static List<T> ToList<T, TChildren>(GatewayData gatewayData, int index)
+        {
+            var flag = true;
+            var list = new List<T>();
+            var i = 0;
+            while (flag)
+            {
+                var type = typeof(T);
+                var obj = Activator.CreateInstance(type);
+                var properties = type.GetProperties();
+                var isFirstProperty = true;
+
+                foreach (var item in properties)
+                {
+                    if (item.PropertyType == typeof(List<TChildren>))
+                    {
+                        var chidrenList = ToList<TChildren, object>(gatewayData, i);
+                        item.SetValue(obj, chidrenList);
+                        continue;
+                    }
+
+                    string key;
+                    var renameAttribute = item.GetCustomAttributes(typeof(ReNameAttribute), true);
+                    if (renameAttribute.Length > 0)
+                    {
+                        key = ((ReNameAttribute)renameAttribute[0]).Name;
+                    }
+                    else
+                    {
+                        key = item.Name.ToSnakeCase();
+                    }
+                    if (index > -1)
+                    {
+                        key += $"_{index}";
+                    }
+                    key += $"_{i}";
+
+                    var value = gatewayData.GetStringValue(key);
+                    if (value == null)
+                    {
+                        if (isFirstProperty)
+                        {
+                            flag = false;
+                            break;
+                        }
+                        continue;
+                    }
+
+                    isFirstProperty = false;
+                    item.SetValue(obj, Convert.ChangeType(value, item.PropertyType));
+                }
+
+                if (!flag)
+                {
+                    return list;
+                }
+
+                list.Add((T)obj);
+                i++;
+            }
+
+            return list;
+        }
+    }
+}

+ 31 - 0
PaySharp.Wechatpay/Domain/AppPayModel.cs

@@ -0,0 +1,31 @@
+using System.ComponentModel.DataAnnotations;
+using PaySharp.Core.Utils;
+
+namespace PaySharp.Wechatpay.Domain
+{
+    public class AppPayModel : BasePayModel
+    {
+        public AppPayModel()
+        {
+            TradeType = "APP";
+        }
+
+        /// <summary>
+        /// 交易类型
+        /// </summary>
+        public string TradeType { get; private set; }
+
+        /// <summary>
+        /// 用户IP
+        /// </summary>
+        [Required(ErrorMessage = "请设置用户IP")]
+        [StringLength(16, ErrorMessage = "用户IP最大长度为16位")]
+        public string SpbillCreateIp { get; set; } = HttpUtil.RemoteIpAddress;
+
+        /// <summary>
+        /// 场景信息,该字段用于上报场景信息,目前支持上报实际门店信息。该字段为JSON对象数据,对象格式为{"store_info":{"id": "门店ID","name": "名称","area_code": "编码","address": "地址" }}
+        /// </summary>
+        [StringLength(256, ErrorMessage = "场景信息最大长度为256位")]
+        public string SceneInfo { get; set; }
+    }
+}

+ 6 - 0
PaySharp.Wechatpay/Domain/AppletPayModel.cs

@@ -0,0 +1,6 @@
+namespace PaySharp.Wechatpay.Domain
+{
+    public class AppletPayModel : PublicPayModel
+    {
+    }
+}

部分文件因文件數量過多而無法顯示