Browse Source

[Alipay] 支持 支付宝 消息服务 #49

Roc 5 years ago
parent
commit
b58da3f1aa

+ 69 - 28
samples/WebApplicationSample/Controllers/NotifyController.cs

@@ -1,6 +1,4 @@
 using System;
-using System.Collections.Generic;
-using System.Text;
 using System.Threading.Tasks;
 using System.Xml;
 using Essensoft.AspNetCore.Payment.Alipay;
@@ -38,39 +36,82 @@ namespace WebApplicationSample.Controllers
             try
             {
                 // 获取参数
-                var Dic = new Dictionary<string, string>();
-                if (Request.Method == "POST")
-                {
-                    foreach (var iter in Request.Form)
-                    {
-                        Dic.Add(iter.Key, iter.Value);
-                    }
-                }
-                else
-                {
-                    foreach (var iter in Request.Query)
-                    {
-                        Dic.Add(iter.Key, iter.Value);
-                    }
-                }
+                var parameters = _client.GetParameters(Request);
 
-                // 激活开发者模式
-                if ("alipay.service.check".Equals(Dic["service"]))
+                parameters.TryGetValue("service", out var service);
+                switch (service)
                 {
-                    var options = _optionsAccessor.Value;
+                    // 激活开发者模式
+                    case "alipay.service.check":
+                        {
+                            var options = _optionsAccessor.Value;
 
-                    var signStr = Dic["sign"];
-                    Dic.Remove("sign");
+                            var sign = parameters["sign"];
+                            parameters.Remove("sign");
 
-                    var signContent = AlipaySignature.GetSignContent(Dic);
+                            var signContent = AlipaySignature.GetSignContent(parameters);
 
-                    // 加签方式为公钥证书时,从公钥证书获取的RSA公钥 options.AlipayPublicCertKey
-                    var isSuccess = AlipaySignature.RSACheckContent(signContent, signStr, options.AlipayPublicCertKey, options.Charset, options.SignType);
+                            // 验签
+                            var isSuccess = AlipaySignature.RSACheckContent(signContent, sign, options.AlipayPublicKey, "GBK", options.SignType);
 
-                    // 组XML响应内容
-                    var response = MakeVerifyGWResponse(isSuccess, options.AlipayPublicCertKey, options.AppPrivateKey, options.Charset, options.SignType);
+                            // 组XML响应内容
+                            var response = MakeVerifyGWResponse(isSuccess, options.AlipayPublicKey, options.AppPrivateKey, "GBK", options.SignType);
 
-                    return Content(response, "text/xml");
+                            return Content(response, "text/xml");
+                        }
+                }
+
+                parameters.TryGetValue("msg_method", out var msg_method);
+                switch (msg_method)
+                {
+                    // 资金单据状态变更通知
+                    case "alipay.fund.trans.order.changed":
+                        {
+                            var notify = await _client.CertificateExecuteAsync<AlipayFundTransOrderChangedNotify>(Request, _optionsAccessor.Value);
+                            return AlipayNotifyResult.Success;
+                        }
+                    // 第三方应用授权取消消息
+                    case "alipay.open.auth.appauth.cancelled":
+                        {
+                            var notify = await _client.CertificateExecuteAsync<AlipayOpenAuthAppauthCancelledNotify>(Request, _optionsAccessor.Value);
+                            return AlipayNotifyResult.Success;
+                        }
+                    // 用户授权取消消息
+                    case "alipay.open.auth.userauth.cancelled":
+                        {
+                            var notify = await _client.CertificateExecuteAsync<AlipayOpenAuthUserauthCancelledNotify>(Request, _optionsAccessor.Value);
+                            return AlipayNotifyResult.Success;
+                        }
+                    // 小程序审核通过通知
+                    case "alipay.open.mini.version.audit.passed":
+                        {
+                            var notify = await _client.CertificateExecuteAsync<AlipayOpenMiniVersionAuditPassedNotify>(Request, _optionsAccessor.Value);
+                            return AlipayNotifyResult.Success;
+                        }
+                    // 用户授权取消消息
+                    case "alipay.open.mini.version.audit.rejected":
+                        {
+                            var notify = await _client.CertificateExecuteAsync<AlipayOpenMiniVersionAuditRejectedNotify>(Request, _optionsAccessor.Value);
+                            return AlipayNotifyResult.Success;
+                        }
+                    // 收单资金结算到银行账户,结算退票的异步通知
+                    case "alipay.trade.settle.dishonoured":
+                        {
+                            var notify = await _client.CertificateExecuteAsync<AlipayTradeSettleDishonouredNotify>(Request, _optionsAccessor.Value);
+                            return AlipayNotifyResult.Success;
+                        }
+                    // 收单资金结算到银行账户,结算失败的异步通知
+                    case "alipay.trade.settle.fail":
+                        {
+                            var notify = await _client.CertificateExecuteAsync<AlipayTradeSettleFailNotify>(Request, _optionsAccessor.Value);
+                            return AlipayNotifyResult.Success;
+                        }
+                    // 收单资金结算到银行账户,结算成功的异步通知
+                    case "alipay.trade.settle.success":
+                        {
+                            var notify = await _client.CertificateExecuteAsync<AlipayTradeSettleSuccessNotify>(Request, _optionsAccessor.Value);
+                            return AlipayNotifyResult.Success;
+                        }
                 }
 
                 return NoContent();

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

@@ -26,7 +26,7 @@
     //应用ID
     "AppId": "",
 
-    //RSA 支付宝公钥,加签方式为公钥证书时,
+    //RSA 支付宝公钥,加签方式为公钥证书时,
     "AlipayPublicKey": "",
 
     //RSA 应用私钥,加签方式为公钥证书时,为证书私钥

+ 1 - 1
samples/WebApplicationSample/appsettings.json

@@ -27,7 +27,7 @@
     //应用ID
     "AppId": "",
 
-    //RSA 支付宝公钥,加签方式为公钥证书时,
+    //RSA 支付宝公钥,加签方式为公钥证书时,
     "AlipayPublicKey": "",
 
     //RSA 应用私钥,加签方式为公钥证书时,为证书私钥

+ 1 - 1
src/Essensoft.AspNetCore.Payment.Alipay/AlipayClient.cs

@@ -471,7 +471,7 @@ namespace Essensoft.AspNetCore.Payment.Alipay
             //为空时添加默认支付宝公钥证书密钥
             if (_certificateManager.IsEmpty)
             {
-                _certificateManager.TryAdd(options.AlipayPublicCertSN, options.AlipayPublicCertKey);
+                _certificateManager.TryAdd(options.AlipayPublicCertSN, options.AlipayPublicKey);
             }
 
             //如果响应的支付宝公钥证书序号已经缓存过,则直接使用缓存的公钥

+ 110 - 0
src/Essensoft.AspNetCore.Payment.Alipay/AlipayFromNotify.cs

@@ -0,0 +1,110 @@
+#if NETCOREAPP3_1
+
+using System.Text.Json.Serialization;
+
+namespace Essensoft.AspNetCore.Payment.Alipay
+{
+    /// <summary>
+    /// 消息服务 - FROM 蚂蚁
+    /// </summary>
+    public class AlipayFromNotify :AlipayNotify 
+    {
+        /// <summary>
+        /// 通知ID
+        /// </summary>
+        [JsonPropertyName("notify_id")]
+        public string NotifyId { get; set; }
+
+        /// <summary>
+        /// 消息发送时的服务端时间
+        /// </summary>
+        [JsonPropertyName("utc_timestamp")]
+        public string UtcTimestamp { get; set; }
+
+        /// <summary>
+        /// 消息接口名称
+        /// </summary>
+        [JsonPropertyName("msg_method")]
+        public string MsgMethod { get; set; }
+
+        /// <summary>
+        /// 消息接受方的应用id
+        /// </summary>
+        [JsonPropertyName("app_id")]
+        public string AppId { get; set; }
+
+        /// <summary>
+        /// 消息类型。目前支持类型:sys:系统消息;usr,用户消息;app,应用消息
+        /// </summary>
+        [JsonPropertyName("msg_type")]
+        public string MsgType { get; set; }
+
+        /// <summary>
+        /// 消息归属的商户支付宝uid。用户消息和应用消息时非空
+        /// </summary>
+        [JsonPropertyName("msg_uid")]
+        public string MsgUid { get; set; }
+
+        /// <summary>
+        /// 消息归属方的应用id。应用消息时非空
+        /// </summary>
+        [JsonPropertyName("msg_app_id")]
+        public string MsgAppId { get; set; }
+
+        /// <summary>
+        /// 版本号(1.1版本为标准消息)
+        /// </summary>
+        [JsonPropertyName("version")]
+        public string Version { get; set; }
+
+        /// <summary>
+        /// 消息报文
+        /// </summary>
+        [JsonPropertyName("biz_content")]
+        public string BizContent { get; set; }
+
+        /// <summary>
+        /// 签名
+        /// </summary>
+        [JsonPropertyName("sign")]
+        public string Sign { get; set; }
+
+        /// <summary>
+        /// 签名类型
+        /// </summary>
+        [JsonPropertyName("sign_type")]
+        public string SignType { get; set; }
+
+        /// <summary>
+        /// 加密算法	
+        /// </summary>
+        [JsonPropertyName("encrypt_type")]
+        public string EncryptType { get; set; }
+
+        /// <summary>
+        /// 编码集,该字符集为验签和解密所需要的字符集	
+        /// </summary>
+        [JsonPropertyName("charset")]
+        public string Charset { get; set; }
+
+        /// <summary>
+        /// [1.0版本老协议参数]通知类型,1.1接口没有该参数	
+        /// </summary>
+        [JsonPropertyName("notify_type")]
+        public string NotifyType { get; set; }
+
+        /// <summary>
+        /// [1.0版本老协议参数]通知时间,北京时区,时间格式为:yyyy-MM-dd HH:mm:ss,如果服务器部署在国外请注意做时区转换。若version=1.1则可以使用utc_timestamp识别时间	
+        /// </summary>
+        [JsonPropertyName("notify_time")]
+        public string NotifyTime { get; set; }
+
+        /// <summary>
+        /// [1.0版本老协议参数]授权方的应用id	
+        /// </summary>
+        [JsonPropertyName("auth_app_id")]
+        public string AuthAppId { get; set; }
+    }
+}
+
+#endif

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

@@ -42,7 +42,7 @@ namespace Essensoft.AspNetCore.Payment.Alipay
 
             var parameters = GetParameters(request);
             var rsp = AlipayDictionaryParser.Parse<T>(parameters);
-            CheckNotifySign(parameters, options, false);
+            CheckNotifySign(parameters, options);
             return Task.FromResult(rsp);
         }
 
@@ -52,32 +52,14 @@ namespace Essensoft.AspNetCore.Payment.Alipay
 
         public Task<T> CertificateExecuteAsync<T>(HttpRequest request, AlipayOptions options) where T : AlipayNotify
         {
-            if (options == null)
-            {
-                throw new ArgumentNullException(nameof(options));
-            }
-
-            if (string.IsNullOrEmpty(options.SignType))
-            {
-                throw new ArgumentNullException(nameof(options.SignType));
-            }
-
-            if (string.IsNullOrEmpty(options.AlipayPublicCertKey))
-            {
-                throw new ArgumentNullException(nameof(options.AlipayPublicCertKey));
-            }
-
-            var parameters = GetParameters(request);
-            var rsp = AlipayDictionaryParser.Parse<T>(parameters);
-            CheckNotifySign(parameters, options, true);
-            return Task.FromResult(rsp);
+            return ExecuteAsync<T>(request, options);
         }
 
         #endregion
 
-        #region Common Method
+        #region IAlipayNotifyClient Members
 
-        private Dictionary<string, string> GetParameters(HttpRequest request)
+        public IDictionary<string, string> GetParameters(HttpRequest request)
         {
             var parameters = new Dictionary<string, string>();
             if (request.Method == "POST")
@@ -97,7 +79,11 @@ namespace Essensoft.AspNetCore.Payment.Alipay
             return parameters;
         }
 
-        private void CheckNotifySign(IDictionary<string, string> dictionary, AlipayOptions options, bool useCert)
+        #endregion
+
+        #region Common Method
+
+        private void CheckNotifySign(IDictionary<string, string> dictionary, AlipayOptions options)
         {
             if (dictionary == null || dictionary.Count == 0)
             {
@@ -110,7 +96,7 @@ namespace Essensoft.AspNetCore.Payment.Alipay
             }
 
             var prestr = GetSignContent(dictionary);
-            if (!AlipaySignature.RSACheckContent(prestr, sign, useCert ? options.AlipayPublicCertKey : options.AlipayPublicKey, options.Charset, options.SignType))
+            if (!AlipaySignature.RSACheckContent(prestr, sign, options.AlipayPublicKey, options.Charset, options.SignType))
             {
                 throw new AlipayException("sign check fail: check Sign Data Fail!");
             }

+ 10 - 13
src/Essensoft.AspNetCore.Payment.Alipay/AlipayOptions.cs

@@ -1,5 +1,4 @@
 using Essensoft.AspNetCore.Payment.Alipay.Utility;
-using Org.BouncyCastle.X509;
 
 namespace Essensoft.AspNetCore.Payment.Alipay
 {
@@ -8,16 +7,14 @@ namespace Essensoft.AspNetCore.Payment.Alipay
     /// </summary>
     public class AlipayOptions
     {
-        internal string AppCertSN;
-        internal X509Certificate AlipayPublicCertificate;
-        internal string AlipayPublicCertSN;
-        public string AlipayPublicCertKey;
-        internal string RootCertSN;
-
         private string appCert;
         private string alipayPublicCert;
         private string rootCert;
 
+        internal string AppCertSN;
+        internal string AlipayPublicCertSN;
+        internal string RootCertSN;
+
         /// <summary>
         /// 蚂蚁金服开放平台 应用ID
         /// </summary>
@@ -25,7 +22,7 @@ namespace Essensoft.AspNetCore.Payment.Alipay
 
         /// <summary>
         /// RSA 支付宝公钥
-        /// 加签方式为公钥证书时,
+        /// 加签方式为公钥证书时,
         /// </summary>
         public string AlipayPublicKey { get; set; }
 
@@ -87,8 +84,8 @@ namespace Essensoft.AspNetCore.Payment.Alipay
                 if (!string.IsNullOrEmpty(value))
                 {
                     appCert = value;
-                    var cert = AntCertificationUtil.ParseCert(value);
-                    AppCertSN = AntCertificationUtil.GetCertSN(cert);
+                    var appCertificate = AntCertificationUtil.ParseCert(value);
+                    AppCertSN = AntCertificationUtil.GetCertSN(appCertificate);
                 }
             }
         }
@@ -104,9 +101,9 @@ namespace Essensoft.AspNetCore.Payment.Alipay
                 if (!string.IsNullOrEmpty(value))
                 {
                     alipayPublicCert = value;
-                    AlipayPublicCertificate = AntCertificationUtil.ParseCert(value);
-                    AlipayPublicCertSN = AntCertificationUtil.GetCertSN(AlipayPublicCertificate);
-                    AlipayPublicCertKey = AntCertificationUtil.ExtractPemPublicKeyFromCert(AlipayPublicCertificate);
+                    var alipayPublicCertificate = AntCertificationUtil.ParseCert(value);
+                    AlipayPublicCertSN = AntCertificationUtil.GetCertSN(alipayPublicCertificate);
+                    AlipayPublicKey = AntCertificationUtil.ExtractPemPublicKeyFromCert(alipayPublicCertificate);
                 }
             }
         }

+ 7 - 0
src/Essensoft.AspNetCore.Payment.Alipay/IAlipayNotifyClient.cs

@@ -1,5 +1,6 @@
 #if NETCOREAPP3_1
 
+using System.Collections.Generic;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Http;
 
@@ -27,6 +28,12 @@ namespace Essensoft.AspNetCore.Payment.Alipay
         /// <param name="options">配置选项</param>
         /// <returns>领域对象</returns>
         Task<T> CertificateExecuteAsync<T>(HttpRequest request, AlipayOptions options) where T : AlipayNotify;
+
+        /// <summary>
+        /// 获取通知参数
+        /// </summary>
+        /// <param name="request"></param>
+        IDictionary<string, string> GetParameters(HttpRequest request);
     }
 }
 

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

@@ -0,0 +1,14 @@
+#if NETCOREAPP3_1
+
+namespace Essensoft.AspNetCore.Payment.Alipay.Notify
+{
+    /// <summary>
+    /// 资金单据状态变更通知
+    /// https://docs.open.alipay.com/msgapi_60/alipay.fund.trans.order.changed/
+    /// </summary>
+    public class AlipayFundTransOrderChangedNotify : AlipayFromNotify
+    {
+    }
+}
+
+#endif

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

@@ -0,0 +1,14 @@
+#if NETCOREAPP3_1
+
+namespace Essensoft.AspNetCore.Payment.Alipay.Notify
+{
+    /// <summary>
+    /// 第三方应用授权取消消息
+    /// https://docs.open.alipay.com/msgapi_54/alipay.open.auth.appauth.cancelled/
+    /// </summary>
+    public class AlipayOpenAuthAppauthCancelledNotify : AlipayFromNotify
+    {
+    }
+}
+
+#endif

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

@@ -0,0 +1,14 @@
+#if NETCOREAPP3_1
+
+namespace Essensoft.AspNetCore.Payment.Alipay.Notify
+{
+    /// <summary>
+    /// 用户授权取消消息
+    /// https://docs.open.alipay.com/msgapi_54/alipay.open.auth.userauth.cancelled/
+    /// </summary>
+    public class AlipayOpenAuthUserauthCancelledNotify : AlipayFromNotify
+    {
+    }
+}
+
+#endif

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

@@ -0,0 +1,14 @@
+#if NETCOREAPP3_1
+
+namespace Essensoft.AspNetCore.Payment.Alipay.Notify
+{
+    /// <summary>
+    /// 小程序审核通过通知
+    /// https://docs.open.alipay.com/msgapi_54/alipay.open.mini.version.audit.passed/
+    /// </summary>
+    public class AlipayOpenMiniVersionAuditPassedNotify : AlipayNotify
+    {
+    }
+}
+
+#endif

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

@@ -0,0 +1,14 @@
+#if NETCOREAPP3_1
+
+namespace Essensoft.AspNetCore.Payment.Alipay.Notify
+{
+    /// <summary>
+    /// 小程序审核驳回通知
+    /// https://docs.open.alipay.com/msgapi_54/alipay.open.mini.version.audit.rejected/
+    /// </summary>
+    public class AlipayOpenMiniVersionAuditRejectedNotify : AlipayNotify
+    {
+    }
+}
+
+#endif

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

@@ -0,0 +1,14 @@
+#if NETCOREAPP3_1
+
+namespace Essensoft.AspNetCore.Payment.Alipay.Notify
+{
+    /// <summary>
+    /// 收单资金结算到银行账户,结算退票的异步通知
+    /// https://docs.open.alipay.com/msgapi_61/alipay.trade.settle.dishonoured/
+    /// </summary>
+    public class AlipayTradeSettleDishonouredNotify : AlipayNotify
+    {
+    }
+}
+
+#endif

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

@@ -0,0 +1,14 @@
+#if NETCOREAPP3_1
+
+namespace Essensoft.AspNetCore.Payment.Alipay.Notify
+{
+    /// <summary>
+    /// 收单资金结算到银行账户,结算失败的异步通知
+    /// https://docs.open.alipay.com/msgapi_61/alipay.trade.settle.fail/
+    /// </summary>
+    public class AlipayTradeSettleFailNotify : AlipayNotify
+    {
+    }
+}
+
+#endif

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

@@ -0,0 +1,14 @@
+#if NETCOREAPP3_1
+
+namespace Essensoft.AspNetCore.Payment.Alipay.Notify
+{
+    /// <summary>
+    /// 收单资金结算到银行账户,结算成功的异步通知
+    /// https://docs.open.alipay.com/msgapi_61/alipay.trade.settle.success/
+    /// </summary>
+    public class AlipayTradeSettleSuccessNotify : AlipayFromNotify
+    {
+    }
+}
+
+#endif

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

@@ -1,6 +1,7 @@
 using System;
 using System.Collections;
 using System.Security.Cryptography;
+using System.Text.Encodings.Web;
 using System.Text.Json;
 using Essensoft.AspNetCore.Payment.Alipay.Utility;
 using Essensoft.AspNetCore.Payment.Security;
@@ -12,6 +13,8 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Parser
     /// </summary>
     public class AlipayJsonParser<T> : IAlipayParser<T> where T : AlipayResponse
     {
+        private static readonly JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions { IgnoreNullValues = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
+
         public string EncryptSourceData(IAlipayRequest<T> request, string body, string encryptType, string encryptKey)
         {
             if (!"AES".Equals(encryptType))
@@ -29,7 +32,7 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Parser
 
         private static string GetSign(string body)
         {
-            var json = JsonSerializer.Deserialize<IDictionary>(body);
+            var json = JsonSerializer.Deserialize<IDictionary>(body, jsonSerializerOptions);
             return json[AlipayConstants.SIGN]?.ToString();
         }
 
@@ -141,7 +144,7 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Parser
             {
                 if (body.StartsWith("{") && body.EndsWith("}"))
                 {
-                    json = JsonSerializer.Deserialize<IDictionary>(body);
+                    json = JsonSerializer.Deserialize<IDictionary>(body, jsonSerializerOptions);
                 }
 
                 if (json != null)
@@ -149,7 +152,7 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Parser
                     // 忽略根节点的名称
                     foreach (var key in json.Keys)
                     {
-                        rsp = JsonSerializer.Deserialize<T>(json[key].ToString());
+                        rsp = JsonSerializer.Deserialize<T>(json[key].ToString(), jsonSerializerOptions);
                         if (rsp != null)
                         {
                             break;
@@ -194,15 +197,13 @@ namespace Essensoft.AspNetCore.Payment.Alipay.Parser
                 return null;
             }
 
-            var certItem = new CertItem();
-
-            var json = JsonSerializer.Deserialize<IDictionary>(responseBody);
-            certItem.Sign = json["sign"]?.ToString();
-            certItem.CertSN = json["alipay_cert_sn"]?.ToString();
-
-            var signSourceData = GetSignSourceData(request, responseBody);
-            certItem.SignSourceDate = signSourceData;
-
+            var json = JsonSerializer.Deserialize<IDictionary>(responseBody, jsonSerializerOptions);
+            var certItem = new CertItem()
+            {
+                Sign = json[AlipayConstants.SIGN]?.ToString(),
+                CertSN = json[AlipayConstants.ALIPAY_CERT_SN]?.ToString(),
+                SignSourceDate = GetSignSourceData(request, responseBody)
+            };
             return certItem;
         }