瀏覽代碼

add JDPay

Roc 7 年之前
父節點
當前提交
2e4a082a44
共有 35 個文件被更改,包括 2318 次插入0 次删除
  1. 55 0
      src/Essensoft.AspNetCore.Payment.JDPay/Domain/PayTradeDetail.cs
  2. 37 0
      src/Essensoft.AspNetCore.Payment.JDPay/Domain/PayTradeVo.cs
  3. 14 0
      src/Essensoft.AspNetCore.Payment.JDPay/Domain/Result.cs
  4. 30 0
      src/Essensoft.AspNetCore.Payment.JDPay/Essensoft.AspNetCore.Payment.JDPay.csproj
  5. 23 0
      src/Essensoft.AspNetCore.Payment.JDPay/IJDPayClient.cs
  6. 33 0
      src/Essensoft.AspNetCore.Payment.JDPay/IJDPayRequest.cs
  7. 217 0
      src/Essensoft.AspNetCore.Payment.JDPay/JDPayClient.cs
  8. 62 0
      src/Essensoft.AspNetCore.Payment.JDPay/JDPayDictionary.cs
  9. 153 0
      src/Essensoft.AspNetCore.Payment.JDPay/JDPayNotifyClient.cs
  10. 6 0
      src/Essensoft.AspNetCore.Payment.JDPay/JDPayNotifyResponse.cs
  11. 6 0
      src/Essensoft.AspNetCore.Payment.JDPay/JDPayObject.cs
  12. 25 0
      src/Essensoft.AspNetCore.Payment.JDPay/JDPayOptions.cs
  13. 56 0
      src/Essensoft.AspNetCore.Payment.JDPay/JDPayResponse.cs
  14. 70 0
      src/Essensoft.AspNetCore.Payment.JDPay/Notify/JDPayAsyncNotifyResponse.cs
  15. 44 0
      src/Essensoft.AspNetCore.Payment.JDPay/Notify/JDPaySyncReturnResponse.cs
  16. 10 0
      src/Essensoft.AspNetCore.Payment.JDPay/Parser/IJDPayParser.cs
  17. 53 0
      src/Essensoft.AspNetCore.Payment.JDPay/Parser/JDPayDictionaryParser.cs
  18. 33 0
      src/Essensoft.AspNetCore.Payment.JDPay/Parser/JDPayXmlParser.cs
  19. 53 0
      src/Essensoft.AspNetCore.Payment.JDPay/Request/JDPayOrderQueryRequest.cs
  20. 53 0
      src/Essensoft.AspNetCore.Payment.JDPay/Request/JDPayQueryRefundRequest.cs
  21. 102 0
      src/Essensoft.AspNetCore.Payment.JDPay/Request/JDPayRefundRequest.cs
  22. 82 0
      src/Essensoft.AspNetCore.Payment.JDPay/Request/JDPayRevokeRequest.cs
  23. 196 0
      src/Essensoft.AspNetCore.Payment.JDPay/Request/JDPaySaveOrderH5Request.cs
  24. 196 0
      src/Essensoft.AspNetCore.Payment.JDPay/Request/JDPaySaveOrderPCRequest.cs
  25. 196 0
      src/Essensoft.AspNetCore.Payment.JDPay/Request/JDPayUnifiedOrderRequest.cs
  26. 47 0
      src/Essensoft.AspNetCore.Payment.JDPay/Response/JDPayOrderQueryResponse.cs
  27. 56 0
      src/Essensoft.AspNetCore.Payment.JDPay/Response/JDPayQueryRefundResponse.cs
  28. 56 0
      src/Essensoft.AspNetCore.Payment.JDPay/Response/JDPayRefundResponse.cs
  29. 63 0
      src/Essensoft.AspNetCore.Payment.JDPay/Response/JDPayRevokeResponse.cs
  30. 9 0
      src/Essensoft.AspNetCore.Payment.JDPay/Response/JDPaySaveOrderResponse.cs
  31. 26 0
      src/Essensoft.AspNetCore.Payment.JDPay/Response/JDPayUnifiedOrderResponse.cs
  32. 26 0
      src/Essensoft.AspNetCore.Payment.JDPay/ServiceCollectionExtensions.cs
  33. 53 0
      src/Essensoft.AspNetCore.Payment.JDPay/Utility/HttpClientEx.cs
  34. 40 0
      src/Essensoft.AspNetCore.Payment.JDPay/Utility/JDPaySecurity.cs
  35. 137 0
      src/Essensoft.AspNetCore.Payment.JDPay/Utility/JDPayUtility.cs

+ 55 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Domain/PayTradeDetail.cs

@@ -0,0 +1,55 @@
+using System.Xml.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Domain
+{
+    public class PayTradeDetail
+    {
+        /// <summary>
+        /// 持卡人人姓名  掩码显示(隐去第一位)
+        /// </summary>
+        [XmlElement("cardHolderName")]
+        public string CardHolderName { get; set; }
+
+        /// <summary>
+        /// 持卡人手机号  掩码显示(手机号的前三位与后四位)
+        /// </summary>
+        [XmlElement("cardHolderMobile")]
+        public string CardHolderMobile { get; set; }
+
+        /// <summary>
+        /// 证件类型   ID("0", "身份证"), PASSPORT("1", "护照"), OFFICER("2", "军官证"), SOLDIER("3", "士兵证"), TWHK_PASSPORT("4", "港奥台通行证"), TEMP_ID("5", "临时身份证"), HOUSEHOLDREGISTER("6", "户口本"), OTHER("7", "其它类型证件")
+        /// </summary>
+        [XmlElement("cardHolderType")]
+        public string CardHolderType { get; set; }
+
+        /// <summary>
+        /// 身份证号
+        /// </summary>
+        [XmlElement("cardHolderId")]
+        public string CardHolderId { get; set; }
+
+        /// <summary>
+        /// 卡号  掩码显示(前六位及后四位)
+        /// </summary>
+        [XmlElement("cardNo")]
+        public string CardNo { get; set; }
+
+        /// <summary>
+        /// 银行编码
+        /// </summary>
+        [XmlElement("bankCode")]
+        public string BankCode { get; set; }
+
+        /// <summary>
+        /// 银行卡类型   DEBIT_CARD:借记卡CREDIT_CARD:信用卡SEMI_CREDIT_CARD:准贷记卡
+        /// </summary>
+        [XmlElement("cardType")]
+        public string CardType { get; set; }
+
+        /// <summary>
+        /// 支付金额
+        /// </summary>
+        [XmlElement("payMoney")]
+        public long PayMoney { get; set; }
+    }
+}

+ 37 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Domain/PayTradeVo.cs

@@ -0,0 +1,37 @@
+using System.Xml.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Domain
+{
+    public class PayTradeVo
+    {
+        /// <summary>
+        /// 支付方式
+        /// </summary>
+        [XmlElement("payType")]
+        public string PayType { get; set; }
+
+        /// <summary>
+        /// 交易金额
+        /// </summary>
+        [XmlElement("amount")]
+        public long Amount { get; set; }
+
+        /// <summary>
+        /// 交易币种
+        /// </summary>
+        [XmlElement("currency")]
+        public string Currency { get; set; }
+
+        /// <summary>
+        /// 交易时间 yyyyMMddHHmmss
+        /// </summary>
+        [XmlElement("tradeTime")]
+        public string TradeTime { get; set; }
+
+        /// <summary>
+        /// 支付明细,不同支付方式的明细信息也不同
+        /// </summary>
+        [XmlElement("detail")]
+        public PayTradeDetail Detail { get; set; }
+    }
+}

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

@@ -0,0 +1,14 @@
+using System.Xml.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Domain
+{
+    [XmlRoot("result")]
+    public class Result
+    {
+        [XmlElement("code")]
+        public string Code { get; set; }
+
+        [XmlElement("desc")]
+        public string Desc { get; set; }
+    }
+}

+ 30 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Essensoft.AspNetCore.Payment.JDPay.csproj

@@ -0,0 +1,30 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <Company>Essensoft</Company>
+    <Authors>Roc</Authors>
+    <Product>Payment</Product>
+    <Version>1.3.0</Version>
+    <AssemblyVersion>1.3.0.0</AssemblyVersion>
+    <FileVersion>1.3.0.0</FileVersion>
+    <Description>ASP.NET Core Payment for JDPay.</Description>
+    <Copyright>© Essensoft 2018</Copyright>
+    <PackageProjectUrl>https://github.com/Essensoft/Payment</PackageProjectUrl>
+    <RepositoryUrl>https://github.com/Essensoft/Payment</RepositoryUrl>
+    <RepositoryType>git</RepositoryType>
+    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
+    <PackageId>Essensoft.AspNetCore.Payment.JDPay</PackageId>
+  </PropertyGroup>
+  
+  <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.Http" Version="2.0.2" />
+    <PackageReference Include="Microsoft.Extensions.Logging" Version="2.0.1" />
+    <PackageReference Include="Microsoft.Extensions.Options" Version="2.0.1" />
+  </ItemGroup>
+  
+  <ItemGroup>
+    <ProjectReference Include="..\Essensoft.AspNetCore.Payment.Security\Essensoft.AspNetCore.Payment.Security.csproj" />
+  </ItemGroup>
+
+</Project>

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

@@ -0,0 +1,23 @@
+using System.Threading.Tasks;
+
+namespace Essensoft.AspNetCore.Payment.JDPay
+{
+    public interface IJDPayClient
+    {
+        /// <summary>
+        /// 执行JDPay API请求。
+        /// </summary>
+        /// <param name="request">具体的JDPay API请求</param>
+        /// <returns>领域对象</returns>
+        Task<T> ExecuteAsync<T>(IJDPayRequest<T> request) where T : JDPayResponse;
+
+
+        /// <summary>
+        /// 执行JDPay API请求。
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="request">具体的JDPay API请求</param>
+        /// <returns></returns>
+        Task<T> PageExecuteAsync<T>(IJDPayRequest<T> request, string reqMethod) where T : JDPayResponse;
+    }
+}

+ 33 - 0
src/Essensoft.AspNetCore.Payment.JDPay/IJDPayRequest.cs

@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+
+namespace Essensoft.AspNetCore.Payment.JDPay
+{
+    public interface IJDPayRequest<T> where T : JDPayResponse
+    {
+        /// <summary>
+        /// API接口地址
+        /// </summary>
+        /// <returns></returns>
+        string GetRequestUrl();
+
+        /// <summary>
+        /// 获取API版本
+        /// </summary>
+        /// <returns></returns>
+        string GetApiVersion();
+
+        /// <summary>
+        /// 设置API版本
+        /// </summary>
+        /// <param name="apiVersion"></param>
+        void SetApiVersion(string apiVersion);
+
+        /// <summary>
+        /// 获取所有的Key-Value形式的文本请求参数字典。其中:
+        /// Key: 请求参数名
+        /// Value: 请求参数文本值
+        /// </summary>
+        /// <returns>文本请求参数字典</returns>
+        IDictionary<string, string> GetParameters();
+    }
+}

+ 217 - 0
src/Essensoft.AspNetCore.Payment.JDPay/JDPayClient.cs

@@ -0,0 +1,217 @@
+using Essensoft.AspNetCore.Payment.JDPay.Parser;
+using Essensoft.AspNetCore.Payment.JDPay.Utility;
+using Essensoft.AspNetCore.Payment.Security;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Org.BouncyCastle.Crypto;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+
+namespace Essensoft.AspNetCore.Payment.JDPay
+{
+    public class JDPayClient : IJDPayClient
+    {
+        private const string VERSION = "version";
+        private const string MERCHANT = "merchant";
+        private const string SIGN = "sign";
+        private const string ENCRYPT = "encrypt";
+        private const string RESULT = "result";
+        private const string BODY = "body";
+
+        private AsymmetricKeyParameter PrivateKey;
+        private AsymmetricKeyParameter PublicKey;
+        private byte[] DesKey;
+
+        public JDPayOptions Options { get; set; }
+
+        public ILogger<JDPayClient> Logger { get; set; }
+
+        protected internal HttpClientEx Client { get; set; }
+
+        public JDPayClient(
+            IOptions<JDPayOptions> optionsAccessor,
+            ILogger<JDPayClient> logger)
+        {
+            Options = optionsAccessor?.Value ?? new JDPayOptions();
+            Logger = logger;
+            Client = new HttpClientEx();
+
+            if (string.IsNullOrEmpty(Options.Merchant))
+            {
+                throw new ArgumentNullException(nameof(Options.Merchant));
+            }
+
+            if (string.IsNullOrEmpty(Options.RsaPrivateKey))
+            {
+                throw new ArgumentNullException(nameof(Options.RsaPrivateKey));
+            }
+
+            if (string.IsNullOrEmpty(Options.RsaPublicKey))
+            {
+                throw new ArgumentNullException(nameof(Options.RsaPublicKey));
+            }
+
+            if (string.IsNullOrEmpty(Options.DesKey))
+            {
+                throw new ArgumentNullException(nameof(Options.DesKey));
+            }
+
+            PrivateKey = RSAUtilities.GetKeyParameterFormPrivateKey(Options.RsaPrivateKey);
+            PublicKey = RSAUtilities.GetKeyParameterFormPublicKey(Options.RsaPublicKey);
+            DesKey = Convert.FromBase64String(Options.DesKey);
+        }
+
+        public void SetTimeout(int timeout)
+        {
+            Client.Timeout = new TimeSpan(0, 0, 0, timeout);
+        }
+
+        public async Task<T> ExecuteAsync<T>(IJDPayRequest<T> request) where T : JDPayResponse
+        {
+            // 字典排序
+            var sortedTxtParams = new JDPayDictionary(request.GetParameters());
+
+            var content = BuildEncryptXml(request, sortedTxtParams);
+            Logger.LogInformation(0, "Request:{content}", content);
+
+            var rspContent = await Client.DoPostAsync(request.GetRequestUrl(), content);
+            Logger.LogInformation(1, "Response:{content}", rspContent);
+
+            var parser = new JDPayXmlParser<T>();
+            var rsp = parser.Parse(JDPayUtility.FotmatXmlString(rspContent));
+            if (!string.IsNullOrEmpty(rsp.Encrypt))
+            {
+                var encrypt = rsp.Encrypt;
+                var base64EncryptStr = Encoding.UTF8.GetString(Convert.FromBase64String(encrypt));
+                var reqBody = DES3.DecryptECB(base64EncryptStr, DesKey);
+                Logger.LogInformation(2, "Encrypt Content:{body}", reqBody);
+
+                var reqBodyDoc = new XmlDocument();
+                reqBodyDoc.LoadXml(reqBody);
+
+                var sign = JDPayUtility.GetValue(reqBodyDoc, "sign");
+                var rootNode = reqBodyDoc.SelectSingleNode("jdpay");
+                var signNode = rootNode.SelectSingleNode("sign");
+                rootNode.RemoveChild(signNode);
+
+                var reqBodyStr = JDPayUtility.ConvertXmlToString(reqBodyDoc);
+                var xmlh = rsp.Body.Substring(0, rsp.Body.IndexOf("<jdpay>"));
+                if (!string.IsNullOrEmpty(xmlh))
+                {
+                    reqBodyStr = reqBodyStr.Replace("<?xml version=\"1.0\" encoding=\"UTF-8\"?>", xmlh);
+                }
+                var sha256SourceSignString = SHA256.Compute(reqBodyStr);
+                var decryptByte = RSA_ECB_PKCS1Padding.Decrypt(Convert.FromBase64String(sign), PublicKey);
+                var decryptStr = DES3.BytesToString(decryptByte);
+                if (sha256SourceSignString == decryptStr)
+                {
+                    rsp = parser.Parse(reqBody);
+                    rsp.Encrypt = encrypt;
+                }
+                else
+                {
+                    throw new Exception("sign check fail: check Sign and Data Fail!");
+                }
+            }
+            return rsp;
+        }
+
+        public Task<T> PageExecuteAsync<T>(IJDPayRequest<T> request, string reqMethod) where T : JDPayResponse
+        {
+            // 字典排序
+            var sortedTxtParams = new JDPayDictionary(request.GetParameters());
+            var encyptParams = BuildEncryptDic(request, sortedTxtParams);
+            var rsp = Activator.CreateInstance<T>();
+
+            var url = request.GetRequestUrl();
+            if (reqMethod == "GET")
+            {
+                //拼接get请求的url
+                var tmpUrl = url;
+                if (encyptParams != null && encyptParams.Count > 0)
+                {
+                    if (tmpUrl.Contains("?"))
+                    {
+                        tmpUrl = tmpUrl + "&" + HttpClientEx.BuildQuery(encyptParams);
+                    }
+                    else
+                    {
+                        tmpUrl = tmpUrl + "?" + HttpClientEx.BuildQuery(encyptParams);
+                    }
+                }
+                rsp.Body = tmpUrl;
+            }
+            else
+            {
+                //输出post表单
+                rsp.Body = BuildHtmlRequest(url, encyptParams);
+            }
+
+            return Task.FromResult(rsp);
+        }
+
+        private string BuildEncryptXml<T>(IJDPayRequest<T> request, JDPayDictionary dic) where T : JDPayResponse
+        {
+            var xmldoc = JDPayUtility.SortedDictionary2AllXml(dic);
+            var smlStr = JDPayUtility.ConvertXmlToString(xmldoc);
+            var sha256SourceSignString = SHA256.Compute(smlStr);
+            var encyptBytes = RSA_ECB_PKCS1Padding.Encrypt(Encoding.UTF8.GetBytes(sha256SourceSignString), PrivateKey);
+            var sign = Convert.ToBase64String(encyptBytes, Base64FormattingOptions.InsertLineBreaks);
+            var data = smlStr.Replace("</jdpay>", "<sign>" + sign + "</sign></jdpay>");
+            var encrypt = DES3.EncryptECB(data, DesKey);
+            // 字典排序
+            var reqdic = new JDPayDictionary
+            {
+                { VERSION, request.GetApiVersion() },
+                { MERCHANT, Options.Merchant },
+                { ENCRYPT, Convert.ToBase64String(Encoding.UTF8.GetBytes(encrypt)) }
+            };
+
+            return JDPayUtility.SortedDictionary2XmlStr(reqdic);
+        }
+
+        private JDPayDictionary BuildEncryptDic<T>(IJDPayRequest<T> request, IDictionary<string, string> dic) where T : JDPayResponse
+        {
+            var signDic = new JDPayDictionary(dic)
+            {
+                { VERSION, request.GetApiVersion() },
+                { MERCHANT, Options.Merchant },
+            };
+
+            var signContent = JDPaySecurity.GetSignContent(signDic);
+            var sign = JDPaySecurity.RSASign(signContent, PrivateKey);
+
+            var encyptDic = new JDPayDictionary
+            {
+                { VERSION, request.GetApiVersion() },
+                { MERCHANT, Options.Merchant },
+                { SIGN, sign }
+            };
+
+            foreach (var item in dic)
+            {
+                if (!string.IsNullOrEmpty(item.Value))
+                {
+                    encyptDic.Add(item.Key, DES3.EncryptECB(item.Value, DesKey));
+                }
+            }
+            return encyptDic;
+        }
+
+        private string BuildHtmlRequest(string url, JDPayDictionary dicPara)
+        {
+            var sbHtml = new StringBuilder();
+            sbHtml.Append("<form id='submit' name='submit' action='" + url + "' method='post' style='display:none;'>");
+            foreach (var temp in dicPara)
+            {
+                sbHtml.Append("<input  name='" + temp.Key + "' value='" + temp.Value + "'/>");
+            }
+            sbHtml.Append("<input type='submit' style='display:none;'></form>");
+            sbHtml.Append("<script>document.forms['submit'].submit();</script>");
+            return sbHtml.ToString();
+        }
+    }
+}

+ 62 - 0
src/Essensoft.AspNetCore.Payment.JDPay/JDPayDictionary.cs

@@ -0,0 +1,62 @@
+using System.Collections.Generic;
+
+namespace Essensoft.AspNetCore.Payment.JDPay
+{
+    public class JDPayDictionary : SortedDictionary<string, string>
+    {
+        public JDPayDictionary() { }
+
+        public JDPayDictionary(IDictionary<string, string> dictionary)
+            : base(dictionary)
+        { }
+
+        public void Add(string key, object value)
+        {
+            string strValue;
+
+            if (value == null)
+            {
+                strValue = null;
+            }
+            else if (value is string)
+            {
+                strValue = (string)value;
+            }
+            else if (value is int?)
+            {
+                strValue = (value as int?).Value.ToString();
+            }
+            else if (value is long?)
+            {
+                strValue = (value as long?).Value.ToString();
+            }
+            else if (value is double?)
+            {
+                strValue = (value as double?).Value.ToString();
+            }
+            else if (value is bool?)
+            {
+                strValue = (value as bool?).Value.ToString().ToLower();
+            }
+            else
+            {
+                strValue = value.ToString();
+            }
+
+            Add(key, strValue);
+        }
+
+        public new void Add(string key, string value)
+        {
+            if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
+            {
+                base.Add(key, value);
+            }
+        }
+
+        public string GetValue(string key)
+        {
+            return TryGetValue(key, out var o) ? o : null;
+        }
+    }
+}

+ 153 - 0
src/Essensoft.AspNetCore.Payment.JDPay/JDPayNotifyClient.cs

@@ -0,0 +1,153 @@
+using Essensoft.AspNetCore.Payment.JDPay.Parser;
+using Essensoft.AspNetCore.Payment.JDPay.Utility;
+using Essensoft.AspNetCore.Payment.Security;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Org.BouncyCastle.Crypto;
+using System;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+
+namespace Essensoft.AspNetCore.Payment.JDPay
+{
+    public class JDPayNotifyClient
+    {
+        private const string SIGN = "sign";
+        private AsymmetricKeyParameter PrivateKey;
+        private AsymmetricKeyParameter PublicKey;
+        private byte[] DesKey;
+
+        public JDPayOptions Options { get; set; }
+
+        public virtual ILogger<JDPayNotifyClient> Logger { get; set; }
+
+        public JDPayNotifyClient(
+            IOptions<JDPayOptions> optionsAccessor,
+            ILogger<JDPayNotifyClient> logger)
+        {
+            Options = optionsAccessor?.Value ?? new JDPayOptions();
+            Logger = logger;
+
+            if (string.IsNullOrEmpty(Options.Merchant))
+            {
+                throw new ArgumentNullException(nameof(Options.Merchant));
+            }
+
+            if (string.IsNullOrEmpty(Options.RsaPrivateKey))
+            {
+                throw new ArgumentNullException(nameof(Options.RsaPrivateKey));
+            }
+
+            if (string.IsNullOrEmpty(Options.RsaPublicKey))
+            {
+                throw new ArgumentNullException(nameof(Options.RsaPublicKey));
+            }
+
+            if (string.IsNullOrEmpty(Options.DesKey))
+            {
+                throw new ArgumentNullException(nameof(Options.DesKey));
+            }
+
+            PrivateKey = RSAUtilities.GetKeyParameterFormPrivateKey(Options.RsaPrivateKey);
+            PublicKey = RSAUtilities.GetKeyParameterFormPublicKey(Options.RsaPublicKey);
+            DesKey = Convert.FromBase64String(Options.DesKey);
+        }
+
+        public async Task<T> ExecuteAsync<T>(HttpRequest request) where T : JDPayNotifyResponse
+        {
+            if (request.HasFormContentType)
+            {
+                var parameters = await GetParametersAsync(request);
+
+                var query = HttpClientEx.BuildQuery(parameters);
+                Logger.LogInformation(0, "Request:{query}", query);
+
+                var parser = new JDPayDictionaryParser<T>();
+                var rsp = parser.Parse(parameters);
+
+                CheckNotifySign(rsp.Parameters, PrivateKey);
+                return rsp;
+            }
+            else if (request.HasTextXmlContentType())
+            {
+                var body = await new StreamReader(request.Body).ReadToEndAsync();
+                Logger.LogInformation(0, "Request:{body}", body);
+
+                var parser = new JDPayXmlParser<T>();
+                var rsp = parser.Parse(JDPayUtility.FotmatXmlString(body));
+                if (!string.IsNullOrEmpty(rsp.Encrypt))
+                {
+                    var encrypt = rsp.Encrypt;
+                    var base64EncryptStr = Encoding.UTF8.GetString(Convert.FromBase64String(encrypt));
+                    var reqBody = DES3.DecryptECB(base64EncryptStr, DesKey);
+                    Logger.LogInformation(1, "Encrypt Content:{body}", reqBody);
+
+                    var reqBodyDoc = new XmlDocument();
+                    reqBodyDoc.LoadXml(reqBody);
+
+                    var sign = JDPayUtility.GetValue(reqBodyDoc, "sign");
+                    var rootNode = reqBodyDoc.SelectSingleNode("jdpay");
+                    var signNode = rootNode.SelectSingleNode("sign");
+                    rootNode.RemoveChild(signNode);
+
+                    var reqBodyStr = JDPayUtility.ConvertXmlToString(reqBodyDoc);
+                    var xmlh = rsp.Body.Substring(0, rsp.Body.IndexOf("<jdpay>"));
+                    if (!string.IsNullOrEmpty(xmlh))
+                    {
+                        reqBodyStr = reqBodyStr.Replace("<?xml version=\"1.0\" encoding=\"UTF-8\"?>", xmlh);
+                    }
+                    var sha256SourceSignString = SHA256.Compute(reqBodyStr);
+                    var decryptByte = RSA_ECB_PKCS1Padding.Decrypt(Convert.FromBase64String(sign), PublicKey);
+                    var decryptStr = DES3.BytesToString(decryptByte);
+                    if (sha256SourceSignString == decryptStr)
+                    {
+                        rsp = parser.Parse(reqBody);
+                        rsp.Encrypt = encrypt;
+                    }
+                    else
+                    {
+                        throw new Exception("sign check fail: check Sign and Data Fail!");
+                    }
+                }
+                return rsp;
+            }
+            else
+            {
+                throw new Exception("sign check fail: check Sign and Data Fail!");
+            }
+        }
+
+        private async Task<JDPayDictionary> GetParametersAsync(HttpRequest request)
+        {
+            var parameters = new JDPayDictionary();
+            var form = await request.ReadFormAsync();
+            foreach (var item in form)
+            {
+                parameters.Add(item.Key, item.Key.Equals(SIGN) ? item.Value.ToString() : DES3.DecryptECB(item.Value, DesKey));
+            }
+            return parameters;
+        }
+
+        private void CheckNotifySign(JDPayDictionary para, object parameters)
+        {
+            if (para.Count == 0)
+            {
+                throw new Exception("sign check fail: para is Empty!");
+            }
+
+            if (!para.TryGetValue("sign", out var sign))
+            {
+                throw new Exception("sign check fail: sign is Empty!");
+            }
+
+            var signContent = JDPaySecurity.GetSignContent(para);
+            if (!JDPaySecurity.RSACheckContent(signContent, sign, PublicKey))
+            {
+                throw new Exception("sign check fail: check Sign and Data Fail");
+            }
+        }
+    }
+}

+ 6 - 0
src/Essensoft.AspNetCore.Payment.JDPay/JDPayNotifyResponse.cs

@@ -0,0 +1,6 @@
+namespace Essensoft.AspNetCore.Payment.JDPay
+{
+    public abstract class JDPayNotifyResponse : JDPayResponse
+    {
+    }
+}

+ 6 - 0
src/Essensoft.AspNetCore.Payment.JDPay/JDPayObject.cs

@@ -0,0 +1,6 @@
+namespace Essensoft.AspNetCore.Payment.JDPay
+{
+    public abstract class JDPayObject
+    {
+    }
+}

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

@@ -0,0 +1,25 @@
+namespace Essensoft.AspNetCore.Payment.JDPay
+{
+    public class JDPayOptions
+    {
+        /// <summary>
+        /// 商户号
+        /// </summary>
+        public string Merchant { get; set; }
+
+        /// <summary>
+        /// 京东DES秘钥
+        /// </summary>
+        public string DesKey { get; set; }
+
+        /// <summary>
+        /// 京东RSA公钥
+        /// </summary>
+        public string RsaPublicKey { get; set; }
+
+        /// <summary>
+        /// 商户RSA私钥
+        /// </summary>
+        public string RsaPrivateKey { get; set; }
+    }
+}

+ 56 - 0
src/Essensoft.AspNetCore.Payment.JDPay/JDPayResponse.cs

@@ -0,0 +1,56 @@
+using Essensoft.AspNetCore.Payment.JDPay.Domain;
+using System.Xml.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.JDPay
+{
+    public abstract class JDPayResponse : JDPayObject
+    {
+        /// <summary>
+        /// 原始内容
+        /// </summary>
+        [XmlIgnore]
+        public string Body { get; set; }
+
+        /// <summary>
+        /// 原始参数
+        /// </summary>
+        [XmlIgnore]
+        public JDPayDictionary Parameters { get; internal set; }
+
+        /// <summary>
+        /// 版本号
+        /// </summary>
+        [XmlElement("version")]
+        public string Version { get; set; }
+
+        /// <summary>
+        /// 商户号
+        /// </summary>
+        [XmlElement("merchant")]
+        public string Merchant { get; set; }
+
+        /// <summary>
+        /// 门店号
+        /// </summary>
+        [XmlElement("device")]
+        public string Device { get; set; }
+
+        /// <summary>
+        /// 返回信息
+        /// </summary>
+        [XmlElement("result")]
+        public Result Result { get; set; }
+
+        /// <summary>
+        /// 数据签名
+        /// </summary>
+        [XmlElement("sign")]
+        public string Sign { get; set; }
+
+        /// <summary>
+        /// 加密报文
+        /// </summary>
+        [XmlElement("encrypt")]
+        public string Encrypt { get; set; }
+    }
+}

+ 70 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Notify/JDPayAsyncNotifyResponse.cs

@@ -0,0 +1,70 @@
+using Essensoft.AspNetCore.Payment.JDPay.Domain;
+using System.Collections.Generic;
+using System.Xml.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Notify
+{
+    [XmlRoot("jdpay")]
+    public class JDPayAsyncNotifyResponse : JDPayNotifyResponse
+    {
+        /// <summary>
+        /// 交易流水  数字或字母
+        /// </summary>
+        [XmlElement("tradeNum")]
+        public string TradeNum { get; set; }
+
+        /// <summary>
+        /// 交易类型
+        /// 0:消费,1:退款
+        /// </summary>
+        [XmlElement("tradeType")]
+        public string TradeType { get; set; }
+
+        // 消费相关字段
+
+        /// <summary>
+        /// 交易列表
+        /// </summary>
+        [XmlArray("payList")]
+        [XmlArrayItem("pay")]
+        public List<PayTradeVo> PayList { get; set; }
+
+        // 退款相关字段
+
+        /// <summary>
+        /// 原交易流水号
+        /// </summary>
+        [XmlElement("oTradeNum")]
+        public string OTradeNum { get; set; }
+
+        /// <summary>
+        /// 交易金额
+        /// </summary>
+        [XmlElement("amount")]
+        public long Amount { get; set; }
+
+        /// <summary>
+        /// 交易币种
+        /// </summary>
+        [XmlElement("currency")]
+        public string Currency { get; set; }
+
+        /// <summary>
+        /// 交易时间
+        /// </summary>
+        [XmlElement("tradeTime")]
+        public string TradeTime { get; set; }
+
+        /// <summary>
+        /// 交易备注
+        /// </summary>
+        [XmlElement("note")]
+        public string Note { get; set; }
+
+        /// <summary>
+        /// 交易状态
+        /// </summary>
+        [XmlElement("status")]
+        public string Status { get; set; }
+    }
+}

+ 44 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Notify/JDPaySyncReturnResponse.cs

@@ -0,0 +1,44 @@
+using System.Xml.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Notify
+{
+    [XmlRoot("jdpay")]
+    public class JDPaySyncReturnResponse : JDPayNotifyResponse
+    {
+        /// <summary>
+        /// 交易流水号
+        /// </summary>
+        [XmlElement("tradeNum")]
+        public string TradeNum { get; set; }
+
+        /// <summary>
+        /// 交易金额
+        /// </summary>
+        [XmlElement("amount")]
+        public long Amount { get; set; }
+
+        /// <summary>
+        /// 货币种类
+        /// </summary>
+        [XmlElement("currency")]
+        public string Currency { get; set; }
+
+        /// <summary>
+        /// 交易时间
+        /// </summary>
+        [XmlElement("tradeTime")]
+        public string TradeTime { get; set; }
+
+        /// <summary>
+        /// 交易备注
+        /// </summary>
+        [XmlElement("note")]
+        public string Note { get; set; }
+
+        /// <summary>
+        /// 交易状态
+        /// </summary>
+        [XmlElement("status")]
+        public string Status { get; set; }
+    }
+}

+ 10 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Parser/IJDPayParser.cs

@@ -0,0 +1,10 @@
+namespace Essensoft.AspNetCore.Payment.JDPay.Parser
+{
+    /// <summary>
+    /// 京东支付结果解析
+    /// </summary>
+    public interface IJDPayParser<T> where T : JDPayResponse
+    {
+        T Parse(string body);
+    }
+}

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

@@ -0,0 +1,53 @@
+using Essensoft.AspNetCore.Payment.JDPay.Utility;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Xml.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Parser
+{
+    public class JDPayDictionaryParser<T> where T : JDPayResponse
+    {
+        private static readonly Dictionary<Type, Dictionary<string, PropertyInfo>> DicProperties = new Dictionary<Type, Dictionary<string, PropertyInfo>>();
+
+        public T Parse(JDPayDictionary dic)
+        {
+            if (dic == null || dic.Count == 0)
+                throw new ArgumentNullException(nameof(dic));
+
+            if (!DicProperties.ContainsKey(typeof(T))) DicProperties[typeof(T)] = GetPropertiesMap(typeof(T));
+
+            var propertiesMap = DicProperties[typeof(T)];
+
+            var rsp = Activator.CreateInstance<T>();
+
+            foreach (var item in dic)
+            {
+                if (propertiesMap.ContainsKey(item.Key))
+                    propertiesMap[item.Key].SetValue(rsp, item.Value.TryTo(propertiesMap[item.Key].PropertyType));
+            }
+
+            if (rsp != null)
+            {
+                rsp.Parameters = dic;
+            }
+
+            return rsp;
+        }
+
+        private Dictionary<string, PropertyInfo> GetPropertiesMap(Type type)
+        {
+            if (type == null)
+                throw new ArgumentNullException(nameof(type));
+            var propertiesMap = new Dictionary<string, PropertyInfo>();
+            var query = from e in typeof(T).GetProperties()
+                        where e.CanWrite && e.GetCustomAttributes(typeof(XmlElementAttribute), true).Any()
+                        select new { Property = e, Element = e.GetCustomAttributes(typeof(XmlElementAttribute), true).OfType<XmlElementAttribute>().First() };
+            foreach (var item in query)
+                propertiesMap.Add(item.Element.ElementName, item.Property);
+
+            return propertiesMap;
+        }
+    }
+}

+ 33 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Parser/JDPayXmlParser.cs

@@ -0,0 +1,33 @@
+using System;
+using System.IO;
+using System.Xml.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Parser
+{
+    public class JDPayXmlParser<T> : IJDPayParser<T> where T : JDPayResponse
+    {
+        public T Parse(string body)
+        {
+            T rsp = null;
+
+            try
+            {
+                using (var sr = new StringReader(body))
+                {
+                    var xmldes = new XmlSerializer(typeof(T));
+                    rsp = (T)xmldes.Deserialize(sr);
+                }
+            }
+            catch
+            { }
+
+            if (rsp == null)
+                rsp = Activator.CreateInstance<T>();
+
+            if (rsp != null)
+                rsp.Body = body;
+
+            return rsp;
+        }
+    }
+}

+ 53 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Request/JDPayOrderQueryRequest.cs

@@ -0,0 +1,53 @@
+using Essensoft.AspNetCore.Payment.JDPay.Response;
+using System.Collections.Generic;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Request
+{
+    /// <summary>
+    /// 交易查询接口
+    /// </summary>
+    public class JDPayOrderQueryRequest : IJDPayRequest<JDPayOrderQueryResponse>
+    {
+        /// <summary>
+        /// 交易流水号
+        /// </summary>
+        public string TradeNum { get; set; }
+
+        /// <summary>
+        /// 原交易流水号
+        /// </summary>
+        public string OTradeNum { get; set; }
+
+        #region IJDPayRequest Members
+
+        private string ApiVersion = "V2.0";
+
+        public string GetRequestUrl()
+        {
+            return "https://paygate.jd.com/service/query";
+        }
+
+        public void SetApiVersion(string apiVersion)
+        {
+            ApiVersion = apiVersion;
+        }
+
+        public string GetApiVersion()
+        {
+            return ApiVersion;
+        }
+
+        public IDictionary<string, string> GetParameters()
+        {
+            var parameters = new JDPayDictionary()
+            {
+                { "tradeNum", TradeNum },
+                { "oTradeNum", OTradeNum },
+                { "tradeType", 0 }, // 消费
+            };
+            return parameters;
+        }
+
+        #endregion
+    }
+}

+ 53 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Request/JDPayQueryRefundRequest.cs

@@ -0,0 +1,53 @@
+using Essensoft.AspNetCore.Payment.JDPay.Response;
+using System.Collections.Generic;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Request
+{
+    /// <summary>
+    /// 正单号退款查询接口
+    /// </summary>
+    public class JDPayQueryRefundRequest : IJDPayRequest<JDPayQueryRefundResponse>
+    {
+        /// <summary>
+        /// 交易流水号
+        /// </summary>
+        public string TradeNum { get; set; }
+
+        /// <summary>
+        /// 原交易流水号
+        /// </summary>
+        public string OTradeNum { get; set; }
+
+        #region IJDPayRequest Members
+
+        private string ApiVersion = "V2.0";
+
+        public string GetRequestUrl()
+        {
+            return "https://paygate.jd.com/service/queryRefund ";
+        }
+
+        public void SetApiVersion(string apiVersion)
+        {
+            ApiVersion = apiVersion;
+        }
+
+        public string GetApiVersion()
+        {
+            return ApiVersion;
+        }
+
+        public IDictionary<string, string> GetParameters()
+        {
+            var parameters = new JDPayDictionary()
+            {
+                { "tradeNum", TradeNum },
+                { "oTradeNum", OTradeNum },
+                { "tradeType", 1 }, // 退款
+            };
+            return parameters;
+        }
+
+        #endregion
+    }
+}

+ 102 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Request/JDPayRefundRequest.cs

@@ -0,0 +1,102 @@
+using Essensoft.AspNetCore.Payment.JDPay.Response;
+using System.Collections.Generic;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Request
+{
+    /// <summary>
+    /// 退款申请接口
+    /// </summary>
+    public class JDPayRefundRequest : IJDPayRequest<JDPayRefundResponse>
+    {
+        /// <summary>
+        /// 交易流水号
+        /// </summary>
+        public string TradeNum { get; set; }
+
+        /// <summary>
+        /// 原交易流水号
+        /// </summary>
+        public string OTradeNum { get; set; }
+
+        /// <summary>
+        /// 交易金额
+        /// 商户订单的资金总额。单位:分,大于0
+        /// </summary>
+        public long Amount { get; set; }
+
+        /// <summary>
+        /// 交易币种
+        /// 货币类型。固定值:CNY
+        /// </summary>
+        public string Currency { get; set; }
+
+        /// <summary>
+        /// 交易时间
+        /// 订单生成时间。格式:“yyyyMMddHHmmss”
+        /// </summary>
+        public string TradeTime { get; set; }
+
+        /// <summary>
+        /// 交易通知地址
+        /// 支付完成后,京东异步通知商户服务相关支付结果。必须是外网可访问的url。
+        /// </summary>
+        public string NotifyUrl { get; set; }
+
+        /// <summary>
+        /// 交易备注
+        /// 退款申请的备注
+        /// </summary>
+        public string Note { get; set; }
+
+        /// <summary>
+        /// 门店号
+        /// 商户门店号
+        /// </summary>
+        public string Device { get; set; }
+
+        /// <summary>
+        /// 终端号
+        /// 用户终端信息,以json格式提交
+        /// </summary>
+        public string TermInfoId { get; set; }
+
+
+        #region IJDPayRequest Members
+
+        private string ApiVersion = "V2.0";
+
+        public string GetRequestUrl()
+        {
+            return "https://paygate.jd.com/service/refund";
+        }
+
+        public void SetApiVersion(string apiVersion)
+        {
+            ApiVersion = apiVersion;
+        }
+
+        public string GetApiVersion()
+        {
+            return ApiVersion;
+        }
+
+        public IDictionary<string, string> GetParameters()
+        {
+            var parameters = new JDPayDictionary()
+            {
+                { "tradeNum", TradeNum },
+                { "oTradeNum", OTradeNum },
+                { "amount", Amount },
+                { "currency", Currency },
+                { "tradeTime", TradeTime },
+                { "notifyUrl", NotifyUrl },
+                { "note", Note },
+                { "device", Device },
+                { "termInfoId", TermInfoId },
+            };
+            return parameters;
+        }
+
+        #endregion
+    }
+}

+ 82 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Request/JDPayRevokeRequest.cs

@@ -0,0 +1,82 @@
+using Essensoft.AspNetCore.Payment.JDPay.Response;
+using System.Collections.Generic;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Request
+{
+    /// <summary>
+    /// 撤销申请接口
+    /// </summary>
+    public class JDPayRevokeRequest : IJDPayRequest<JDPayRevokeResponse>
+    {
+        /// <summary>
+        /// 交易流水号
+        /// 和原交易流水号不能一致,否则撤销失败
+        /// </summary>
+        public string TradeNum { get; set; }
+
+        /// <summary>
+        /// 原交易流水号
+        /// 数字或字母,标识需要撤销的那笔交易流水。
+        /// </summary>
+        public string OTradeNum { get; set; }
+
+        /// <summary>
+        /// 交易金额
+        /// 商户订单的资金总额。单位:分,大于0
+        /// </summary>
+        public string Amount { get; set; }
+
+        /// <summary>
+        /// 交易币种
+        /// 货币类型。固定值:CNY
+        /// </summary>
+        public string Currency { get; set; }
+
+        /// <summary>
+        /// 交易时间
+        /// 订单生成时间。格式:“yyyyMMddHHmmss”
+        /// </summary>
+        public string TradeTime { get; set; }
+
+        /// <summary>
+        /// 交易备注
+        /// 商户备注信息
+        /// </summary>
+        public string Note { get; set; }
+
+        #region IJDPayRequest Members
+
+        private string ApiVersion = "V2.0";
+
+        public string GetRequestUrl()
+        {
+            return "https://paygate.jd.com/service/revoke";
+        }
+
+        public void SetApiVersion(string apiVersion)
+        {
+            ApiVersion = apiVersion;
+        }
+
+        public string GetApiVersion()
+        {
+            return ApiVersion;
+        }
+
+        public IDictionary<string, string> GetParameters()
+        {
+            var parameters = new JDPayDictionary()
+            {
+                { "tradeNum", TradeNum },
+                { "oTradeNum", OTradeNum },
+                { "amount", Amount },
+                { "currency", Currency },
+                { "tradeTime", TradeTime },
+                { "note", Note },
+            };
+            return parameters;
+        }
+
+        #endregion
+    }
+}

+ 196 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Request/JDPaySaveOrderH5Request.cs

@@ -0,0 +1,196 @@
+using Essensoft.AspNetCore.Payment.JDPay.Response;
+using System.Collections.Generic;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Request
+{
+    /// <summary>
+    /// H5在线支付接口
+    /// </summary>
+    public class JDPaySaveOrderH5Request : IJDPayRequest<JDPaySaveOrderResponse>
+    {
+        /// <summary>
+        /// 门店号
+        /// </summary>
+        public string Device { get; set; }
+
+        /// <summary>
+        /// 交易流水号
+        /// </summary>
+        public string TradeNum { get; set; }
+
+        /// <summary>
+        /// 交易名称
+        /// </summary>
+        public string TradeName { get; set; }
+
+        /// <summary>
+        /// 交易描述
+        /// </summary>
+        public string TradeDesc { get; set; }
+
+        /// <summary>
+        /// 交易时间
+        /// </summary>
+        public string TradeTime { get; set; }
+
+        /// <summary>
+        /// 交易金额
+        /// </summary>
+        public string Amount { get; set; }
+
+        /// <summary>
+        /// 订单类型
+        /// </summary>
+        public string OrderType { get; set; }
+
+        /// <summary>
+        /// 业务类型
+        /// </summary>
+        public string IndustryCategoryCode { get; set; }
+
+        /// <summary>
+        /// 货币种类
+        /// </summary>
+        public string Currency { get; set; }
+
+        /// <summary>
+        /// 商户备注
+        /// </summary>
+        public string Note { get; set; }
+
+        /// <summary>
+        /// 支付成功跳转路径
+        /// </summary>
+        public string CallbackUrl { get; set; }
+
+        /// <summary>
+        /// 异步通知页面地址
+        /// </summary>
+        public string NotifyUrl { get; set; }
+
+        /// <summary>
+        /// 用户IP
+        /// </summary>
+        public string IP { get; set; }
+
+        /// <summary>
+        /// 用户指定卡号
+        /// </summary>
+        public string SpecCardNo { get; set; }
+
+        /// <summary>
+        /// 用户指定身份证
+        /// </summary>
+        public string SpecId { get; set; }
+
+        /// <summary>
+        /// 用户指定姓名
+        /// </summary>
+        public string SpecName { get; set; }
+
+        /// <summary>
+        /// 用户账号
+        /// </summary>
+        public string UserId { get; set; }
+
+        /// <summary>
+        /// 订单失效时长
+        /// </summary>
+        public string ExpireTime { get; set; }
+
+        /// <summary>
+        /// 订单商品数量
+        /// </summary>
+        public string OrderGoodsNum { get; set; }
+
+        /// <summary>
+        /// 厂商编码
+        /// </summary>
+        public string VendorId { get; set; }
+
+        /// <summary>
+        /// 商品信息
+        /// </summary>
+        public string GoodsInfo { get; set; }
+
+        /// <summary>
+        /// 收货信息
+        /// </summary>
+        public string ReceiverInfo { get; set; }
+
+        /// <summary>
+        /// 终端信息
+        /// </summary>
+        public string TermInfo { get; set; }
+
+        /// <summary>
+        /// 风控信息
+        /// </summary>
+        public string RiskInfo { get; set; }
+
+        /// <summary>
+        /// 结算币种
+        /// </summary>
+        public string SettleCurrency { get; set; }
+
+        /// <summary>
+        /// 业务信息
+        /// </summary>
+        public string KJInfo { get; set; }
+
+        #region IJDPayRequest Members
+
+        private string ApiVersion = "V2.0";
+
+        public string GetRequestUrl()
+        {
+            return "https://h5pay.jd.com/jdpay/saveOrder";
+        }
+
+        public void SetApiVersion(string apiVersion)
+        {
+            ApiVersion = apiVersion;
+        }
+
+        public string GetApiVersion()
+        {
+            return ApiVersion;
+        }
+
+        public IDictionary<string, string> GetParameters()
+        {
+            var parameters = new JDPayDictionary()
+            {
+                { "device", Device },
+                { "tradeNum", TradeNum },
+                { "tradeName", TradeName },
+                { "tradeDesc", TradeDesc },
+                { "tradeTime", TradeTime },
+                { "amount", Amount },
+                { "orderType", OrderType },
+                { "industryCategoryCode", IndustryCategoryCode },
+                { "currency", Currency },
+                { "note", Note },
+                { "callbackUrl", CallbackUrl },
+                { "notifyUrl", NotifyUrl },
+                { "ip", IP },
+                { "specCardNo", SpecCardNo },
+                { "specId", SpecId },
+                { "specName", SpecName },
+                { "userId", UserId },
+                { "expireTime", ExpireTime },
+                { "orderGoodsNum", OrderGoodsNum },
+                { "vendorId", VendorId },
+                { "goodsInfo", GoodsInfo },
+                { "receiverInfo", ReceiverInfo },
+                { "termInfo", TermInfo },
+                { "riskInfo", RiskInfo },
+                { "settleCurrency", SettleCurrency },
+                { "kjInfo", KJInfo },
+            };
+            return parameters;
+        }
+
+        #endregion
+    }
+}

+ 196 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Request/JDPaySaveOrderPCRequest.cs

@@ -0,0 +1,196 @@
+using Essensoft.AspNetCore.Payment.JDPay.Response;
+using System.Collections.Generic;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Request
+{
+    /// <summary>
+    /// PC在线支付接口
+    /// </summary>
+    public class JDPaySaveOrderPCRequest : IJDPayRequest<JDPaySaveOrderResponse>
+    {
+        /// <summary>
+        /// 门店号
+        /// </summary>
+        public string Device { get; set; }
+
+        /// <summary>
+        /// 交易流水号
+        /// </summary>
+        public string TradeNum { get; set; }
+
+        /// <summary>
+        /// 交易名称
+        /// </summary>
+        public string TradeName { get; set; }
+
+        /// <summary>
+        /// 交易描述
+        /// </summary>
+        public string TradeDesc { get; set; }
+
+        /// <summary>
+        /// 交易时间
+        /// </summary>
+        public string TradeTime { get; set; }
+
+        /// <summary>
+        /// 交易金额
+        /// </summary>
+        public string Amount { get; set; }
+
+        /// <summary>
+        /// 订单类型
+        /// </summary>
+        public string OrderType { get; set; }
+
+        /// <summary>
+        /// 业务类型
+        /// </summary>
+        public string IndustryCategoryCode { get; set; }
+
+        /// <summary>
+        /// 货币种类
+        /// </summary>
+        public string Currency { get; set; }
+
+        /// <summary>
+        /// 商户备注
+        /// </summary>
+        public string Note { get; set; }
+
+        /// <summary>
+        /// 支付成功跳转路径
+        /// </summary>
+        public string CallbackUrl { get; set; }
+
+        /// <summary>
+        /// 异步通知页面地址
+        /// </summary>
+        public string NotifyUrl { get; set; }
+
+        /// <summary>
+        /// 用户IP
+        /// </summary>
+        public string IP { get; set; }
+
+        /// <summary>
+        /// 用户指定卡号
+        /// </summary>
+        public string SpecCardNo { get; set; }
+
+        /// <summary>
+        /// 用户指定身份证
+        /// </summary>
+        public string SpecId { get; set; }
+
+        /// <summary>
+        /// 用户指定姓名
+        /// </summary>
+        public string SpecName { get; set; }
+
+        /// <summary>
+        /// 用户账号
+        /// </summary>
+        public string UserId { get; set; }
+
+        /// <summary>
+        /// 订单失效时长
+        /// </summary>
+        public string ExpireTime { get; set; }
+
+        /// <summary>
+        /// 订单商品数量
+        /// </summary>
+        public string OrderGoodsNum { get; set; }
+
+        /// <summary>
+        /// 厂商编码
+        /// </summary>
+        public string VendorId { get; set; }
+
+        /// <summary>
+        /// 商品信息
+        /// </summary>
+        public string GoodsInfo { get; set; }
+
+        /// <summary>
+        /// 收货信息
+        /// </summary>
+        public string ReceiverInfo { get; set; }
+
+        /// <summary>
+        /// 终端信息
+        /// </summary>
+        public string TermInfo { get; set; }
+
+        /// <summary>
+        /// 风控信息
+        /// </summary>
+        public string RiskInfo { get; set; }
+
+        /// <summary>
+        /// 结算币种
+        /// </summary>
+        public string SettleCurrency { get; set; }
+
+        /// <summary>
+        /// 业务信息
+        /// </summary>
+        public string KJInfo { get; set; }
+
+        #region IJDPayRequest Members
+
+        private string ApiVersion = "V2.0";
+
+        public string GetRequestUrl()
+        {
+            return "https://wepay.jd.com/jdpay/saveOrder";
+        }
+
+        public void SetApiVersion(string apiVersion)
+        {
+            ApiVersion = apiVersion;
+        }
+
+        public string GetApiVersion()
+        {
+            return ApiVersion;
+        }
+
+        public IDictionary<string, string> GetParameters()
+        {
+            var parameters = new JDPayDictionary()
+            {
+                { "device", Device },
+                { "tradeNum", TradeNum },
+                { "tradeName", TradeName },
+                { "tradeDesc", TradeDesc },
+                { "tradeTime", TradeTime },
+                { "amount", Amount },
+                { "orderType", OrderType },
+                { "industryCategoryCode", IndustryCategoryCode },
+                { "currency", Currency },
+                { "note", Note },
+                { "callbackUrl", CallbackUrl },
+                { "notifyUrl", NotifyUrl },
+                { "ip", IP },
+                { "specCardNo", SpecCardNo },
+                { "specId", SpecId },
+                { "specName", SpecName },
+                { "userId", UserId },
+                { "expireTime", ExpireTime },
+                { "orderGoodsNum", OrderGoodsNum },
+                { "vendorId", VendorId },
+                { "goodsInfo", GoodsInfo },
+                { "receiverInfo", ReceiverInfo },
+                { "termInfo", TermInfo },
+                { "riskInfo", RiskInfo },
+                { "settleCurrency", SettleCurrency },
+                { "kjInfo", KJInfo },
+            };
+            return parameters;
+        }
+
+        #endregion
+    }
+}

+ 196 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Request/JDPayUnifiedOrderRequest.cs

@@ -0,0 +1,196 @@
+using Essensoft.AspNetCore.Payment.JDPay.Response;
+using System.Collections.Generic;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Request
+{
+    /// <summary>
+    /// 统一下单接口
+    /// </summary>
+    public class JDPayUnifiedOrderRequest : IJDPayRequest<JDPayUnifiedOrderResponse>
+    {
+        /// <summary>
+        /// 收款商户
+        /// </summary>
+        public string PayMerchant { get; set; }
+
+        /// <summary>
+        /// 门店号
+        /// </summary>
+        public string Device { get; set; }
+
+        /// <summary>
+        /// 交易流水号
+        /// </summary>
+        public string TradeNum { get; set; }
+
+        /// <summary>
+        /// 交易名称
+        /// </summary>
+        public string TradeName { get; set; }
+
+        /// <summary>
+        /// 交易描述
+        /// </summary>
+        public string TradeDesc { get; set; }
+
+        /// <summary>
+        /// 交易时间
+        /// </summary>
+        public string TradeTime { get; set; }
+
+        /// <summary>
+        /// 交易金额
+        /// </summary>
+        public string Amount { get; set; }
+
+        /// <summary>
+        /// 订单类型
+        /// </summary>
+        public string OrderType { get; set; }
+
+        /// <summary>
+        /// 业务类型
+        /// </summary>
+        public string IndustryCategoryCode { get; set; }
+
+        /// <summary>
+        /// 货币种类
+        /// </summary>
+        public string Currency { get; set; }
+
+        /// <summary>
+        /// 商户备注
+        /// </summary>
+        public string Note { get; set; }
+
+        /// <summary>
+        /// 同步通知页面
+        /// </summary>
+        public string CallbackUrl { get; set; }
+
+        /// <summary>
+        /// 异步通知页面地址
+        /// </summary>
+        public string NotifyUrl { get; set; }
+
+        /// <summary>
+        /// 用户IP
+        /// </summary>
+        public string Ip { get; set; }
+
+        /// <summary>
+        /// 用户指定卡号
+        /// </summary>
+        public string SpecCardNo { get; set; }
+
+        /// <summary>
+        /// 用户指定身份证
+        /// </summary>
+        public string SpecId { get; set; }
+
+        /// <summary>
+        /// 用户指定姓名
+        /// </summary>
+        public string SpecName { get; set; }
+
+        /// <summary>
+        /// 交易类型
+        /// </summary>
+        public string TradeType { get; set; }
+
+        /// <summary>
+        /// 用户账号
+        /// </summary>
+        public string UserId { get; set; }
+
+        /// <summary>
+        /// 订单失效时长
+        /// </summary>
+        public string ExpireTime { get; set; }
+
+        /// <summary>
+        /// 订单商品数量
+        /// </summary>
+        public string OrderGoodsNum { get; set; }
+
+        /// <summary>
+        /// 厂商编码
+        /// </summary>
+        public string VendorId { get; set; }
+
+        /// <summary>
+        /// 商品信息
+        /// </summary>
+        public string GoodsInfo { get; set; }
+
+        /// <summary>
+        /// 收货信息
+        /// </summary>
+        public string ReceiverInfo { get; set; }
+
+        /// <summary>
+        /// 终端信息
+        /// </summary>
+        public string TermInfo { get; set; }
+
+        /// <summary>
+        /// 风控信息
+        /// </summary>
+        public string RiskInfo { get; set; }
+
+        #region IJDPayRequest Members
+
+        private string ApiVersion = "V2.0";
+
+        public string GetRequestUrl()
+        {
+            return "http://paygate.jd.com/service/uniorder";
+        }
+
+        public void SetApiVersion(string apiVersion)
+        {
+            ApiVersion = apiVersion;
+        }
+
+        public string GetApiVersion()
+        {
+            return ApiVersion;
+        }
+
+        public IDictionary<string, string> GetParameters()
+        {
+            var parameters = new JDPayDictionary()
+            {
+                { "payMerchant", PayMerchant },
+                { "device", Device},
+                { "tradeNum",TradeNum },
+                { "tradeName", TradeName},
+                { "tradeDesc", TradeDesc },
+                { "tradeTime", TradeTime },
+                { "amount", Amount },
+                { "orderType", OrderType },
+                { "industryCategoryCode", IndustryCategoryCode },
+                { "currency", Currency },
+                { "note", Note },
+                { "callbackUrl", CallbackUrl},
+                { "notifyUrl", NotifyUrl },
+                { "ip", Ip },
+                { "specCardNo", SpecCardNo },
+                { "specId", SpecId },
+                { "specName", SpecName },
+                { "tradeType", TradeType },
+                { "userId", UserId },
+                { "expireTime", ExpireTime },
+                { "orderGoodsNum", OrderGoodsNum },
+                { "vendorId", VendorId },
+                { "goodsInfo", GoodsInfo },
+                { "receiverInfo", ReceiverInfo },
+                { "termInfo", TermInfo },
+                { "riskInfo", RiskInfo },
+            };
+            return parameters;
+        }
+
+        #endregion
+    }
+}

+ 47 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Response/JDPayOrderQueryResponse.cs

@@ -0,0 +1,47 @@
+using Essensoft.AspNetCore.Payment.JDPay.Domain;
+using System.Collections.Generic;
+using System.Xml.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Response
+{
+    [XmlRoot("jdpay")]
+    public class JDPayOrderQueryResponse : JDPayResponse
+    {
+        /// <summary>
+        /// 交易流水  数字或字母
+        /// </summary>
+        [XmlElement("tradeNum")]
+        public string TradeNum { get; set; }
+
+        /// <summary>
+        /// 交易类型
+        /// </summary>
+        [XmlElement("tradeType")]
+        public int TradeType { get; set; }
+
+        /// <summary>
+        /// 交易备注
+        /// </summary>
+        [XmlElement("note")]
+        public string Note { get; set; }
+
+        /// <summary>
+        /// 支付总金额
+        /// </summary>
+        [XmlElement("amount")]
+        public long Amount { get; set; }
+
+        /// <summary>
+        /// 交易返回状态  成功:2,失败,3
+        /// </summary>
+        [XmlElement("status")]
+        public string Status { get; set; }
+
+        /// <summary>
+        /// 交易列表
+        /// </summary>
+        [XmlArray("payList")]
+        [XmlArrayItem("pay")]
+        public List<PayTradeVo> PayList { get; set; }
+    }
+}

+ 56 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Response/JDPayQueryRefundResponse.cs

@@ -0,0 +1,56 @@
+using System.Xml.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Response
+{
+    [XmlRoot("jdpay")]
+    public class JDPayQueryRefundResponse : JDPayResponse
+    {
+        /// <summary>
+        /// 交易流水
+        /// </summary>
+        [XmlElement("tradeNum")]
+        public string TradeNum { get; set; }
+
+        /// <summary>
+        /// 交易类型
+        /// </summary>
+        [XmlElement("tradeType")]
+        public string TradeType { get; set; }
+
+        /// <summary>
+        /// 原交易流水
+        /// </summary>
+        [XmlElement("oTradeNum")]
+        public string OTradeNum { get; set; }
+
+        /// <summary>
+        /// 交易金额
+        /// </summary>
+        [XmlElement("amount")]
+        public long Amount { get; set; }
+
+        /// <summary>
+        /// 交易币种
+        /// </summary>
+        [XmlElement("currency")]
+        public string Currency { get; set; }
+
+        /// <summary>
+        /// 交易时间
+        /// </summary>
+        [XmlElement("tradeTime")]
+        public string TradeTime { get; set; }
+
+        /// <summary>
+        /// 交易备注
+        /// </summary>
+        [XmlElement("note")]
+        public string Note { get; set; }
+
+        /// <summary>
+        /// 交易状态
+        /// </summary>
+        [XmlElement("status")]
+        public string Status { get; set; }
+    }
+}

+ 56 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Response/JDPayRefundResponse.cs

@@ -0,0 +1,56 @@
+using System.Xml.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Response
+{
+    [XmlRoot("jdpay")]
+    public class JDPayRefundResponse : JDPayResponse
+    {
+        /// <summary>
+        /// 交易流水
+        /// </summary>
+        [XmlElement("tradeNum")]
+        public string TradeNum { get; set; }
+
+        /// <summary>
+        /// 交易类型
+        /// </summary>
+        [XmlElement("tradeType")]
+        public string TradeType { get; set; }
+
+        /// <summary>
+        /// 原交易流水
+        /// </summary>
+        [XmlElement("oTradeNum")]
+        public string OTradeNum { get; set; }
+
+        /// <summary>
+        /// 交易金额
+        /// </summary>
+        [XmlElement("amount")]
+        public long Amount { get; set; }
+
+        /// <summary>
+        /// 交易币种
+        /// </summary>
+        [XmlElement("currency")]
+        public string Currency { get; set; }
+
+        /// <summary>
+        /// 交易时间
+        /// </summary>
+        [XmlElement("tradeTime")]
+        public string TradeTime { get; set; }
+
+        /// <summary>
+        /// 交易备注
+        /// </summary>
+        [XmlElement("note")]
+        public string Note { get; set; }
+
+        /// <summary>
+        /// 交易状态
+        /// </summary>
+        [XmlElement("status")]
+        public string Status { get; set; }
+    }
+}

+ 63 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Response/JDPayRevokeResponse.cs

@@ -0,0 +1,63 @@
+using System.Xml.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Response
+{
+    [XmlRoot("jdpay")]
+    public class JDPayRevokeResponse : JDPayResponse
+    {
+        /// <summary>
+        /// 交易流水
+        /// 商户唯一交易流水号。格式:字母&数字
+        /// </summary>
+        [XmlElement("tradeNum")]
+        public string TradeNum { get; set; }
+
+        /// <summary>
+        /// 交易类型
+        /// 2:撤销
+        /// </summary>
+        [XmlElement("tradeType")]
+        public int TradeType { get; set; }
+
+        /// <summary>
+        /// 原交易流水号	
+        /// 标识需要撤销的那笔交易流水。
+        /// </summary>
+        [XmlElement("oTradeNum")]
+        public string OTradeNum { get; set; }
+
+        /// <summary>
+        /// 交易金额	
+        /// 商户订单的资金总额。单位:分,大于0
+        /// </summary>
+        [XmlElement("amount")]
+        public int Amount { get; set; }
+
+        /// <summary>
+        /// 交易币种
+        /// 货币类型。固定值:CNY
+        /// </summary>
+        [XmlElement("currency")]
+        public int Currency { get; set; }
+
+        /// <summary>
+        /// 交易时间	
+        /// 订单生成时间。格式:“yyyyMMddHHmmss”
+        /// </summary>
+        [XmlElement("tradeTime")]
+        public int TradeTime { get; set; }
+
+        /// <summary>
+        /// 商户备注信息
+        /// </summary>
+        [XmlElement("note")]
+        public string Note { get; set; }
+
+        /// <summary>
+        /// 交易状态
+        /// 1-成功 2-失败
+        /// </summary>
+        [XmlElement("status")]
+        public string Status { get; set; }
+    }
+}

+ 9 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Response/JDPaySaveOrderResponse.cs

@@ -0,0 +1,9 @@
+using System.Xml.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Response
+{
+    [XmlRoot("jdpay")]
+    public class JDPaySaveOrderResponse : JDPayResponse
+    {
+    }
+}

+ 26 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Response/JDPayUnifiedOrderResponse.cs

@@ -0,0 +1,26 @@
+using System.Xml.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Response
+{
+    [XmlRoot("jdpay")]
+    public class JDPayUnifiedOrderResponse : JDPayResponse
+    {
+        [XmlElement("orderId")]
+        public string OrderId { get; set; }
+
+        [XmlElement("merchantName")]
+        public string MerchantName { get; set; }
+
+        [XmlElement("amount")]
+        public string Amount { get; set; }
+
+        [XmlElement("tradeNum")]
+        public string TradeNum { get; set; }
+
+        [XmlElement("qrCode")]
+        public string QrCode { get; set; }
+
+        [XmlElement("expireTime")]
+        public string ExpireTime { get; set; }
+    }
+}

+ 26 - 0
src/Essensoft.AspNetCore.Payment.JDPay/ServiceCollectionExtensions.cs

@@ -0,0 +1,26 @@
+using Essensoft.AspNetCore.Payment.JDPay;
+using System;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+    public static class ServiceCollectionExtensions
+    {
+        public static void AddJDPay(
+            this IServiceCollection services)
+        {
+            services.AddJDPay(setupAction: null);
+        }
+
+        public static void AddJDPay(
+            this IServiceCollection services,
+            Action<JDPayOptions> setupAction)
+        {
+            services.AddSingleton<JDPayClient>();
+            services.AddSingleton<JDPayNotifyClient>();
+            if (setupAction != null)
+            {
+                services.Configure(setupAction);
+            }
+        }
+    }
+}

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

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

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

@@ -0,0 +1,40 @@
+using Essensoft.AspNetCore.Payment.Security;
+using Org.BouncyCastle.Crypto;
+using System;
+using System.Text;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Utility
+{
+    public class JDPaySecurity
+    {
+        public static string GetSignContent(JDPayDictionary para)
+        {
+            if (para == null || para.Count == 0)
+                return string.Empty;
+
+            var sb = new StringBuilder();
+            foreach (var iter in para)
+            {
+                if (!string.IsNullOrEmpty(iter.Value) && iter.Key != "sign")
+                    sb.Append(iter.Key).Append("=").Append(iter.Value).Append("&");
+            }
+
+            return sb.Remove(sb.Length - 1, 1).ToString();
+        }
+
+        public static string RSASign(string sourceSignString, AsymmetricKeyParameter privateKey)
+        {
+            var sha256SourceSignString = SHA256.Compute(sourceSignString);
+            var newsks = RSA_ECB_PKCS1Padding.Encrypt(Encoding.UTF8.GetBytes(sha256SourceSignString), privateKey);
+            return Convert.ToBase64String(newsks, Base64FormattingOptions.InsertLineBreaks);
+        }
+
+        public static bool RSACheckContent(string content, string sign, AsymmetricKeyParameter publicKey)
+        {
+            var sha256SourceSignString = SHA256.Compute(content);
+            var decryptArr = RSA_ECB_PKCS1Padding.Decrypt(Convert.FromBase64String(sign), publicKey);
+            var decrypStr = Encoding.UTF8.GetString(decryptArr);
+            return sha256SourceSignString.Equals(decrypStr);
+        }
+    }
+}

+ 137 - 0
src/Essensoft.AspNetCore.Payment.JDPay/Utility/JDPayUtility.cs

@@ -0,0 +1,137 @@
+using Microsoft.AspNetCore.Http;
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Linq.Expressions;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Xml;
+
+namespace Essensoft.AspNetCore.Payment.JDPay.Utility
+{
+    public static class JDPayUtility
+    {
+        internal static bool HasTextXmlContentType(this HttpRequest request)
+        {
+            // Content-Type: text/xml
+            MediaTypeHeaderValue.TryParse(request.ContentType, out var contentType);
+            return contentType != null && contentType.MediaType.Equals("text/xml", StringComparison.OrdinalIgnoreCase);
+        }
+
+        internal static object TryTo<T>(this object Object)
+        {
+            return Object.TryTo(typeof(T));
+        }
+
+        /// <summary>
+        /// 尝试将对象实例转换成目标类型
+        /// </summary>
+        /// <typeparam name="T">对象实例类型</typeparam>
+        /// <typeparam name="R">目标类型</typeparam>
+        /// <param name="Object">对象实例</param>
+        /// <param name="DefaultValue">默认值</param>
+        /// <returns>转换后类型</returns>
+        internal static object TryTo(this object Object, Type destinationType)
+        {
+            try
+            {
+                if (Object == null || Convert.IsDBNull(Object))
+                    return GetDefault(destinationType);
+                if ((Object as string) != null)
+                {
+                    var ObjectValue = Object as string;
+                    if (destinationType.IsEnum)
+                        return System.Enum.Parse(destinationType, ObjectValue, true);
+                    if (string.IsNullOrEmpty(ObjectValue))
+                        return GetDefault(destinationType);
+                }
+                if ((Object as IConvertible) != null)
+                {
+                    var destination =
+                       destinationType.IsGenericType && destinationType.GetGenericTypeDefinition() == typeof(Nullable<>) ?
+                           Nullable.GetUnderlyingType(destinationType) : destinationType;
+                    return Convert.ChangeType(Object, destination);
+                }
+                if (destinationType.IsAssignableFrom(Object.GetType()))
+                    return Object;
+                var Converter = TypeDescriptor.GetConverter(Object.GetType());
+                if (Converter.CanConvertTo(destinationType))
+                    return Converter.ConvertTo(Object, destinationType);
+            }
+            catch { }
+            return GetDefault(destinationType);
+        }
+
+        private static object GetDefault(Type type)
+        {
+            var defaultExpr = Expression.Default(type);
+            return Expression.Lambda<Func<object>>(defaultExpr).Compile()();
+        }
+
+        public static string GetValue(XmlDocument doc, string name)
+        {
+            var nodeList = doc.GetElementsByTagName(name);
+            if (nodeList != null && nodeList.Count > 0)
+            {
+                return nodeList[0].InnerText;
+            }
+            return string.Empty;
+        }
+
+        public static XmlDocument SortedDictionary2AllXml(JDPayDictionary dic)
+        {
+            var xmldoc = new XmlDocument();
+            var xmldecl = xmldoc.CreateXmlDeclaration("1.0", "UTF-8", null);
+            xmldoc.AppendChild(xmldecl);
+            SortedDictionary2Xml(xmldoc, dic);
+            return xmldoc;
+        }
+
+        public static string SortedDictionary2XmlStr(JDPayDictionary dic)
+        {
+            var xmldoc = new XmlDocument();
+            SortedDictionary2Xml(xmldoc, dic);
+            return ConvertXmlToString(xmldoc);
+        }
+
+        public static void SortedDictionary2Xml(XmlDocument xmldoc, JDPayDictionary dic)
+        {
+            var xmlelem = xmldoc.CreateElement("", "jdpay", "");
+            xmldoc.AppendChild(xmlelem);
+            foreach (var kv in dic)
+            {
+                var xe = xmldoc.CreateElement(kv.Key);
+                xe.InnerText = kv.Value;
+                xmlelem.AppendChild(xe);
+            }
+        }
+
+        public static string ConvertXmlToString(XmlDocument xmlDoc)
+        {
+            var stream = new MemoryStream();
+            var writer = new XmlTextWriter(stream, Encoding.UTF8)
+            {
+                Formatting = Formatting.Indented
+            };
+            xmlDoc.Save(writer);
+            var sr = new StreamReader(stream, Encoding.UTF8);
+            stream.Position = 0;
+            var xmlString = sr.ReadToEnd();
+            sr.Close();
+            stream.Close();
+            return FotmatXmlString(xmlString);
+        }
+
+        public static string FotmatXmlString(string xmlString)
+        {
+            xmlString = xmlString.Replace("\r", "");
+            xmlString = xmlString.Replace("\n", "");
+            xmlString = xmlString.Replace("\t", "");
+            xmlString = Regex.Replace(xmlString, @">\s+<", "><");
+            xmlString = Regex.Replace(xmlString, @"\s+/>", "/>");
+            xmlString = xmlString.Replace("encoding=\"utf-8\"", "encoding=\"UTF-8\"");
+            return xmlString;
+        }
+    }
+}