浏览代码

[WeChatPay.V3] 支持微信支付公钥

Roc 11 月之前
父节点
当前提交
2e4976e5f2
共有 26 个文件被更改,包括 146 次插入175 次删除
  1. 2 2
      Directory.Build.props
  2. 1 1
      global.json
  3. 7 4
      samples/WebApplicationSample/appsettings.Development.json
  4. 7 4
      samples/WebApplicationSample/appsettings.json
  5. 0 30
      src/Essensoft.Paylink.Security/OaepSHA1WithRSA.cs
  6. 0 35
      src/Essensoft.Paylink.Security/SHA256WithRSA.cs
  7. 1 3
      src/Essensoft.Paylink.WeChatPay/V2/Request/WeChatPayTransitPayPayApplyRequest.cs
  8. 1 3
      src/Essensoft.Paylink.WeChatPay/V2/Request/WeChatPayTransitPayQueryStateRequest.cs
  9. 1 4
      src/Essensoft.Paylink.WeChatPay/V2/Response/WeChatPayTransitPayQueryStateResponse.cs
  10. 1 3
      src/Essensoft.Paylink.WeChatPay/V3/Domain/AccountCertInfo.cs
  11. 1 3
      src/Essensoft.Paylink.WeChatPay/V3/Domain/AppInfo.cs
  12. 1 4
      src/Essensoft.Paylink.WeChatPay/V3/Domain/BizStoreInfo.cs
  13. 1 3
      src/Essensoft.Paylink.WeChatPay/V3/Domain/MiniProgramInfo.cs
  14. 1 3
      src/Essensoft.Paylink.WeChatPay/V3/Domain/MpInfo.cs
  15. 0 2
      src/Essensoft.Paylink.WeChatPay/V3/Domain/SaleInfo.cs
  16. 1 3
      src/Essensoft.Paylink.WeChatPay/V3/Domain/WeWorkInfo.cs
  17. 1 4
      src/Essensoft.Paylink.WeChatPay/V3/Domain/WebInfo.cs
  18. 1 1
      src/Essensoft.Paylink.WeChatPay/V3/Extensions/HttpClientExtensions.cs
  19. 1 1
      src/Essensoft.Paylink.WeChatPay/V3/Request/WeChatPayAppSdkRequest.cs
  20. 1 1
      src/Essensoft.Paylink.WeChatPay/V3/Request/WeChatPayJsApiSdkRequest.cs
  21. 1 1
      src/Essensoft.Paylink.WeChatPay/V3/Request/WeChatPayMiniProgramSdkRequest.cs
  22. 64 23
      src/Essensoft.Paylink.WeChatPay/V3/WeChatPayClient.cs
  23. 24 6
      src/Essensoft.Paylink.WeChatPay/V3/WeChatPayNotifyClient.cs
  24. 6 1
      src/Essensoft.Paylink.WeChatPay/V3/WeChatPayPlatformCertificate.cs
  25. 4 1
      src/Essensoft.Paylink.WeChatPay/V3/WeChatPayPlatformCertificateManager.cs
  26. 17 29
      src/Essensoft.Paylink.WeChatPay/WeChatPayOptions.cs

+ 2 - 2
Directory.Build.props

@@ -21,8 +21,8 @@
     <GenerateDocumentationFile Condition="'$(Configuration)' == 'Release'">true</GenerateDocumentationFile>
     <GeneratePackageOnBuild Condition="'$(Configuration)' == 'Release'">true</GeneratePackageOnBuild>
     <LangVersion>latest</LangVersion>
-    <SystemTextJsonPackageVersion>8.0.3</SystemTextJsonPackageVersion>
-    <MicrosoftExtensionsHttpPackageVersion>8.0.0</MicrosoftExtensionsHttpPackageVersion>
+    <SystemTextJsonPackageVersion>8.0.5</SystemTextJsonPackageVersion>
+    <MicrosoftExtensionsHttpPackageVersion>8.0.1</MicrosoftExtensionsHttpPackageVersion>
   </PropertyGroup>
 
   <ItemGroup>

+ 1 - 1
global.json

@@ -1,6 +1,6 @@
 {
   "sdk": {
-    "version": "8.0.204",
+    "version": "8.0.403",
     "rollForward": "latestFeature"
   }
 }

+ 7 - 4
samples/WebApplicationSample/appsettings.Development.json

@@ -35,10 +35,13 @@
     // 可为证书文件路径 / 证书文件的base64字符串
     "Certificate": "",
 
-    // 商户API私钥
-    // 当配置了P12格式证书时,已包含私钥,不必再单独配置API私钥。
-    // PEM格式证书,需要单独配置。
-    "APIPrivateKey": "",
+    // 微信支付公钥Id
+    // 使用V3接口微信支付公钥时必填
+    "WeChatPayPublicKeyId": "",
+
+    // 微信支付公钥
+    // 使用V3接口微信支付公钥时必填
+    "WeChatPayPublicKey": "",
 
     // RSA公钥
     // 目前仅调用"企业付款到银行卡API [V2]"时使用,执行本示例中的"获取RSA加密公钥API [V2]"即可获取。

+ 7 - 4
samples/WebApplicationSample/appsettings.json

@@ -36,10 +36,13 @@
     // 可为证书文件路径 / 证书文件的base64字符串
     "Certificate": "",
 
-    // 商户API私钥
-    // 当配置了P12格式证书时,已包含私钥,不必再单独配置API私钥。
-    // PEM格式证书,需要单独配置。
-    "APIPrivateKey": "",
+    // 微信支付公钥Id
+    // 使用V3接口微信支付公钥时必填
+    "WeChatPayPublicKeyId": "",
+
+    // 微信支付公钥
+    // 使用V3接口微信支付公钥时必填
+    "WeChatPayPublicKey": "",
 
     // RSA公钥
     // 目前仅调用"企业付款到银行卡API [V2]"时使用,执行本示例中的"获取RSA加密公钥API [V2]"即可获取。

+ 0 - 30
src/Essensoft.Paylink.Security/OaepSHA1WithRSA.cs

@@ -43,35 +43,5 @@ namespace Essensoft.Paylink.Security
                 return Encoding.UTF8.GetString(rsa.Decrypt(Convert.FromBase64String(data), RSAEncryptionPadding.OaepSHA1));
             }
         }
-
-        public static string Encrypt(RSA rsa, string data)
-        {
-            if (rsa == null)
-            {
-                throw new ArgumentNullException(nameof(rsa));
-            }
-
-            if (string.IsNullOrEmpty(data))
-            {
-                throw new ArgumentNullException(nameof(data));
-            }
-
-            return Convert.ToBase64String(rsa.Encrypt(Encoding.UTF8.GetBytes(data), RSAEncryptionPadding.OaepSHA1));
-        }
-
-        public static string Decrypt(RSA rsa, string data)
-        {
-            if (rsa == null)
-            {
-                throw new ArgumentNullException(nameof(rsa));
-            }
-
-            if (string.IsNullOrEmpty(data))
-            {
-                throw new ArgumentNullException(nameof(data));
-            }
-
-            return Encoding.UTF8.GetString(rsa.Decrypt(Convert.FromBase64String(data), RSAEncryptionPadding.OaepSHA1));
-        }
     }
 }

+ 0 - 35
src/Essensoft.Paylink.Security/SHA256WithRSA.cs

@@ -48,40 +48,5 @@ namespace Essensoft.Paylink.Security
                 return rsa.VerifyData(Encoding.UTF8.GetBytes(data), Convert.FromBase64String(sign), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
             }
         }
-
-        public static string Sign(RSA rsa, string data)
-        {
-            if (rsa == null)
-            {
-                throw new ArgumentNullException(nameof(rsa));
-            }
-
-            if (string.IsNullOrEmpty(data))
-            {
-                throw new ArgumentNullException(nameof(data));
-            }
-
-            return Convert.ToBase64String(rsa.SignData(Encoding.UTF8.GetBytes(data), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
-        }
-
-        public static bool Verify(RSA rsa, string data, string sign)
-        {
-            if (rsa == null)
-            {
-                throw new ArgumentNullException(nameof(rsa));
-            }
-
-            if (string.IsNullOrEmpty(data))
-            {
-                throw new ArgumentNullException(nameof(data));
-            }
-
-            if (string.IsNullOrEmpty(sign))
-            {
-                throw new ArgumentNullException(nameof(sign));
-            }
-
-            return rsa.VerifyData(Encoding.UTF8.GetBytes(data), Convert.FromBase64String(sign), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
-        }
     }
 }

+ 1 - 3
src/Essensoft.Paylink.WeChatPay/V2/Request/WeChatPayTransitPayPayApplyRequest.cs

@@ -1,6 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
+using System.Collections.Generic;
 using Essensoft.Paylink.WeChatPay.V2.Response;
 
 namespace Essensoft.Paylink.WeChatPay.V2.Request

+ 1 - 3
src/Essensoft.Paylink.WeChatPay/V2/Request/WeChatPayTransitPayQueryStateRequest.cs

@@ -1,6 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
+using System.Collections.Generic;
 using Essensoft.Paylink.WeChatPay.V2.Response;
 
 namespace Essensoft.Paylink.WeChatPay.V2.Request

+ 1 - 4
src/Essensoft.Paylink.WeChatPay/V2/Response/WeChatPayTransitPayQueryStateResponse.cs

@@ -1,7 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Xml.Serialization;
+using System.Xml.Serialization;
 
 namespace Essensoft.Paylink.WeChatPay.V2.Response
 {

+ 1 - 3
src/Essensoft.Paylink.WeChatPay/V3/Domain/AccountCertInfo.cs

@@ -1,6 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
+using System.Collections.Generic;
 using System.Text.Json.Serialization;
 
 namespace Essensoft.Paylink.WeChatPay.V3.Domain

+ 1 - 3
src/Essensoft.Paylink.WeChatPay/V3/Domain/AppInfo.cs

@@ -1,6 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
+using System.Collections.Generic;
 using System.Text.Json.Serialization;
 
 namespace Essensoft.Paylink.WeChatPay.V3.Domain

+ 1 - 4
src/Essensoft.Paylink.WeChatPay/V3/Domain/BizStoreInfo.cs

@@ -1,7 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Text.Json;
+using System.Collections.Generic;
 using System.Text.Json.Serialization;
 
 namespace Essensoft.Paylink.WeChatPay.V3.Domain

+ 1 - 3
src/Essensoft.Paylink.WeChatPay/V3/Domain/MiniProgramInfo.cs

@@ -1,6 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
+using System.Collections.Generic;
 using System.Text.Json.Serialization;
 
 namespace Essensoft.Paylink.WeChatPay.V3.Domain

+ 1 - 3
src/Essensoft.Paylink.WeChatPay/V3/Domain/MpInfo.cs

@@ -1,6 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
+using System.Collections.Generic;
 using System.Text.Json.Serialization;
 
 namespace Essensoft.Paylink.WeChatPay.V3.Domain

+ 0 - 2
src/Essensoft.Paylink.WeChatPay/V3/Domain/SaleInfo.cs

@@ -1,6 +1,4 @@
-using System;
 using System.Collections.Generic;
-using System.Text;
 using System.Text.Json.Serialization;
 
 namespace Essensoft.Paylink.WeChatPay.V3.Domain

+ 1 - 3
src/Essensoft.Paylink.WeChatPay/V3/Domain/WeWorkInfo.cs

@@ -1,6 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
+using System.Collections.Generic;
 using System.Text.Json.Serialization;
 
 namespace Essensoft.Paylink.WeChatPay.V3.Domain

+ 1 - 4
src/Essensoft.Paylink.WeChatPay/V3/Domain/WebInfo.cs

@@ -1,7 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Text.Json.Serialization;
+using System.Text.Json.Serialization;
 
 namespace Essensoft.Paylink.WeChatPay.V3.Domain
 {

+ 1 - 1
src/Essensoft.Paylink.WeChatPay/V3/Extensions/HttpClientExtensions.cs

@@ -195,7 +195,7 @@ namespace Essensoft.Paylink.WeChatPay.V3.Extensions
             var timestamp = WeChatPayUtility.GetTimeStamp();
             var nonce = WeChatPayUtility.GenerateNonceStr();
             var message = BuildMessage(method, uri, timestamp, nonce, body);
-            var signature = SHA256WithRSA.Sign(options.RSAPrivateKey, message);
+            var signature = SHA256WithRSA.Sign(message, options.RSAPrivateKey);
 
             return $"mchid=\"{options.MchId}\",nonce_str=\"{nonce}\",timestamp=\"{timestamp}\",serial_no=\"{options.CertificateSerialNo}\",signature=\"{signature}\"";
         }

+ 1 - 1
src/Essensoft.Paylink.WeChatPay/V3/Request/WeChatPayAppSdkRequest.cs

@@ -58,7 +58,7 @@ namespace Essensoft.Paylink.WeChatPay.V3.Request
             sortedTxtParams.Add(WeChatPayConsts.timestamp, WeChatPayUtility.GetTimeStamp());
 
             var signatureSourceData = BuildSignatureSourceData(sortedTxtParams);
-            sortedTxtParams.Add(WeChatPayConsts.sign, SHA256WithRSA.Sign(options.RSAPrivateKey, signatureSourceData));
+            sortedTxtParams.Add(WeChatPayConsts.sign, SHA256WithRSA.Sign(signatureSourceData, options.RSAPrivateKey));
         }
 
         private static string BuildSignatureSourceData(WeChatPayDictionary sortedTxtParams)

+ 1 - 1
src/Essensoft.Paylink.WeChatPay/V3/Request/WeChatPayJsApiSdkRequest.cs

@@ -47,7 +47,7 @@ namespace Essensoft.Paylink.WeChatPay.V3.Request
             sortedTxtParams.Add(WeChatPayConsts.signType, WeChatPayConsts.RSA);
 
             var signatureSourceData = BuildSignatureSourceData(sortedTxtParams);
-            sortedTxtParams.Add(WeChatPayConsts.paySign, SHA256WithRSA.Sign(options.RSAPrivateKey, signatureSourceData));
+            sortedTxtParams.Add(WeChatPayConsts.paySign, SHA256WithRSA.Sign(signatureSourceData, options.RSAPrivateKey));
         }
 
         private static string BuildSignatureSourceData(WeChatPayDictionary sortedTxtParams)

+ 1 - 1
src/Essensoft.Paylink.WeChatPay/V3/Request/WeChatPayMiniProgramSdkRequest.cs

@@ -47,7 +47,7 @@ namespace Essensoft.Paylink.WeChatPay.V3.Request
             sortedTxtParams.Add(WeChatPayConsts.signType, WeChatPayConsts.RSA);
 
             var signatureSourceData = BuildSignatureSourceData(sortedTxtParams);
-            sortedTxtParams.Add(WeChatPayConsts.paySign, SHA256WithRSA.Sign(options.RSAPrivateKey, signatureSourceData));
+            sortedTxtParams.Add(WeChatPayConsts.paySign, SHA256WithRSA.Sign(signatureSourceData, options.RSAPrivateKey));
         }
 
         private static string BuildSignatureSourceData(WeChatPayDictionary sortedTxtParams)

+ 64 - 23
src/Essensoft.Paylink.WeChatPay/V3/WeChatPayClient.cs

@@ -2,8 +2,6 @@
 using System.Collections.Generic;
 using System.Net.Http;
 using System.Reflection;
-using System.Security.Cryptography;
-using System.Security.Cryptography.X509Certificates;
 using System.Threading.Tasks;
 using Essensoft.Paylink.Security;
 using Essensoft.Paylink.WeChatPay.V3.Extensions;
@@ -161,13 +159,25 @@ namespace Essensoft.Paylink.WeChatPay.V3
                 throw new WeChatPayException($"options.{nameof(WeChatPayOptions.APIv3Key)} is Empty!");
             }
 
-            var cert = await _platformCertificateManager.GetCertificateAsync(this, options);
+            string certSerialNo, certPublicKey;
+
+            if (string.IsNullOrEmpty(options.WeChatPayPublicKeyId) || string.IsNullOrEmpty(options.WeChatPayPublicKey))
+            {
+                var cert = await _platformCertificateManager.GetCertificateAsync(this, options);
+                certSerialNo = cert.SerialNo;
+                certPublicKey = cert.PublicKey;
+            }
+            else
+            {
+                certSerialNo = options.WeChatPayPublicKeyId;
+                certPublicKey = options.WeChatPayPublicKey;
+            }
 
             // 加密敏感信息
-            EncryptPrivacyProperty(request.GetQueryModel(), cert.Certificate.GetRSAPublicKey());
+            EncryptPrivacyProperty(request.GetQueryModel(), certPublicKey);
 
             var client = _httpClientFactory.CreateClient(Name);
-            var (headers, body, statusCode) = await client.GetAsync(request, options, cert.SerialNo);
+            var (headers, body, statusCode) = await client.GetAsync(request, options, certSerialNo);
             var parser = new WeChatPayResponseJsonParser<T>();
             var response = parser.Parse(body, statusCode);
 
@@ -206,13 +216,25 @@ namespace Essensoft.Paylink.WeChatPay.V3
                 throw new WeChatPayException($"options.{nameof(WeChatPayOptions.Certificate)} is Empty!");
             }
 
-            var cert = await _platformCertificateManager.GetCertificateAsync(this, options);
+            string certSerialNo, certPublicKey;
+
+            if (string.IsNullOrEmpty(options.WeChatPayPublicKeyId) || string.IsNullOrEmpty(options.WeChatPayPublicKey))
+            {
+                var cert = await _platformCertificateManager.GetCertificateAsync(this, options);
+                certSerialNo = cert.SerialNo;
+                certPublicKey = cert.PublicKey;
+            }
+            else
+            {
+                certSerialNo = options.WeChatPayPublicKeyId;
+                certPublicKey = options.WeChatPayPublicKey;
+            }
 
             // 加密敏感信息
-            EncryptPrivacyProperty(request.GetBodyModel(), cert.Certificate.GetRSAPublicKey());
+            EncryptPrivacyProperty(request.GetBodyModel(), certPublicKey);
 
             var client = _httpClientFactory.CreateClient(Name);
-            var (headers, body, statusCode) = await client.PostAsync(request, options, cert.SerialNo);
+            var (headers, body, statusCode) = await client.PostAsync(request, options, certSerialNo);
             var parser = new WeChatPayResponseJsonParser<T>();
             var response = parser.Parse(body, statusCode);
 
@@ -243,12 +265,31 @@ namespace Essensoft.Paylink.WeChatPay.V3
                 throw new WeChatPayException($"sign check fail: {nameof(headers.Signature)} is empty!");
             }
 
-            var cert = await _platformCertificateManager.GetCertificateAsync(this, options, headers.Serial);
-            var signSourceData = WeChatPayUtility.BuildSignatureSourceData(headers.Timestamp, headers.Nonce, body);
-            var signCheck = SHA256WithRSA.Verify(cert.Certificate.GetRSAPublicKey(), signSourceData, headers.Signature);
-            if (!signCheck)
+            if (headers.Serial.StartsWith("PUB_KEY_ID_")) // 微信支付公钥
             {
-                throw new WeChatPayException("sign check fail: check Sign and Data Fail!");
+                if (!string.IsNullOrEmpty(options.WeChatPayPublicKeyId) && headers.Serial == options.WeChatPayPublicKeyId)
+                {
+                    var signSourceData = WeChatPayUtility.BuildSignatureSourceData(headers.Timestamp, headers.Nonce, body);
+                    var signCheck = SHA256WithRSA.Verify(options.WeChatPayPublicKey, signSourceData, headers.Signature);
+                    if (!signCheck)
+                    {
+                        throw new WeChatPayException("sign check fail: check Sign and Data Fail!");
+                    }
+                }
+                else
+                {
+                    throw new WeChatPayException("sign check fail: WeChatPay Public Key Id Fail!");
+                }
+            }
+            else
+            {
+                var cert = await _platformCertificateManager.GetCertificateAsync(this, options, headers.Serial);
+                var signSourceData = WeChatPayUtility.BuildSignatureSourceData(headers.Timestamp, headers.Nonce, body);
+                var signCheck = SHA256WithRSA.Verify(signSourceData, headers.Signature, cert.PublicKey);
+                if (!signCheck)
+                {
+                    throw new WeChatPayException("sign check fail: check Sign and Data Fail!");
+                }
             }
         }
 
@@ -262,7 +303,7 @@ namespace Essensoft.Paylink.WeChatPay.V3
         /// <remarks>
         /// <para><a href="https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_3.shtml">敏感信息加解密</a></para>
         /// </remarks>
-        private static void EncryptPrivacyProperty(WeChatPayObject obj, RSA rsa)
+        private static void EncryptPrivacyProperty(WeChatPayObject obj, string publicKey)
         {
             if(obj == null)
             {
@@ -287,7 +328,7 @@ namespace Essensoft.Paylink.WeChatPay.V3
                     }
 
                     // 加密并将密文设置回对象
-                    var ciphertext = OaepSHA1WithRSA.Encrypt(rsa, strValue);
+                    var ciphertext = OaepSHA1WithRSA.Encrypt(strValue, publicKey);
                     propertyInfo.SetValue(obj, ciphertext);
                 }
                 else if (propertyInfo.PropertyType.IsClass) // 加密子对象
@@ -298,7 +339,7 @@ namespace Essensoft.Paylink.WeChatPay.V3
                         {
                             foreach (var item in array)
                             {
-                                EncryptPrivacyProperty(item, rsa);
+                                EncryptPrivacyProperty(item, publicKey);
                             }
                         }
                     }
@@ -308,7 +349,7 @@ namespace Essensoft.Paylink.WeChatPay.V3
                         {
                             foreach (var item in enumerable)
                             {
-                                EncryptPrivacyProperty(item, rsa);
+                                EncryptPrivacyProperty(item, publicKey);
                             }
                         }
                     }
@@ -316,7 +357,7 @@ namespace Essensoft.Paylink.WeChatPay.V3
                     {
                         if (propertyInfo.GetValue(obj) is WeChatPayObject wcpObj)
                         {
-                            EncryptPrivacyProperty(wcpObj, rsa);
+                            EncryptPrivacyProperty(wcpObj, publicKey);
                         }
                     }
                 }
@@ -329,7 +370,7 @@ namespace Essensoft.Paylink.WeChatPay.V3
         /// <remarks>
         /// <para><a href="https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_3.shtml">敏感信息加解密</a></para>
         /// </remarks>
-        private static void DecryptPrivacyProperty(WeChatPayObject obj, RSA rsa)
+        private static void DecryptPrivacyProperty(WeChatPayObject obj, string privateKey)
         {
             if(obj == null)
             {
@@ -354,7 +395,7 @@ namespace Essensoft.Paylink.WeChatPay.V3
                     }
 
                     // 解密并将明文设置回对象
-                    var ciphertext = OaepSHA1WithRSA.Decrypt(rsa, strValue);
+                    var ciphertext = OaepSHA1WithRSA.Decrypt(strValue, privateKey);
                     propertyInfo.SetValue(obj, ciphertext);
                 }
                 else if (propertyInfo.PropertyType.IsClass) // 解密子对象
@@ -365,7 +406,7 @@ namespace Essensoft.Paylink.WeChatPay.V3
                         {
                             foreach (var item in array)
                             {
-                                DecryptPrivacyProperty(item, rsa);
+                                DecryptPrivacyProperty(item, privateKey);
                             }
                         }
                     }
@@ -375,7 +416,7 @@ namespace Essensoft.Paylink.WeChatPay.V3
                         {
                             foreach (var item in enumerable)
                             {
-                                DecryptPrivacyProperty(item, rsa);
+                                DecryptPrivacyProperty(item, privateKey);
                             }
                         }
                     }
@@ -383,7 +424,7 @@ namespace Essensoft.Paylink.WeChatPay.V3
                     {
                         if (propertyInfo.GetValue(obj) is WeChatPayObject wcpObj)
                         {
-                            DecryptPrivacyProperty(wcpObj, rsa);
+                            DecryptPrivacyProperty(wcpObj, privateKey);
                         }
                     }
                 }

+ 24 - 6
src/Essensoft.Paylink.WeChatPay/V3/WeChatPayNotifyClient.cs

@@ -1,7 +1,6 @@
 using System;
 using System.IO;
 using System.Linq;
-using System.Security.Cryptography.X509Certificates;
 using System.Text;
 using System.Threading.Tasks;
 using Essensoft.Paylink.Security;
@@ -115,12 +114,31 @@ namespace Essensoft.Paylink.WeChatPay.V3
                 throw new WeChatPayException("sign check fail: body is empty!");
             }
 
-            var cert = await _platformCertificateManager.GetCertificateAsync(_client, options, headers.Serial);
-            var signSourceData = WeChatPayUtility.BuildSignatureSourceData(headers.Timestamp, headers.Nonce, body);
-            var signCheck = SHA256WithRSA.Verify(cert.Certificate.GetRSAPublicKey(), signSourceData, headers.Signature);
-            if (!signCheck)
+            if(headers.Serial.StartsWith("PUB_KEY_ID_")) // 微信支付公钥
             {
-                throw new WeChatPayException("sign check fail: check Sign and Data Fail!");
+                if (!string.IsNullOrEmpty(options.WeChatPayPublicKeyId) && headers.Serial == options.WeChatPayPublicKeyId)
+                {
+                    var signSourceData = WeChatPayUtility.BuildSignatureSourceData(headers.Timestamp, headers.Nonce, body);
+                    var signCheck = SHA256WithRSA.Verify(options.WeChatPayPublicKey, signSourceData, headers.Signature);
+                    if (!signCheck)
+                    {
+                        throw new WeChatPayException("sign check fail: check Sign and Data Fail!");
+                    }
+                }
+                else
+                {
+                    throw new WeChatPayException("sign check fail: WeChatPay Public Key Id Fail!");
+                }
+            }
+            else
+            {
+                var cert = await _platformCertificateManager.GetCertificateAsync(_client, options, headers.Serial);
+                var signSourceData = WeChatPayUtility.BuildSignatureSourceData(headers.Timestamp, headers.Nonce, body);
+                var signCheck = SHA256WithRSA.Verify(signSourceData, headers.Signature, cert.PublicKey);
+                if (!signCheck)
+                {
+                    throw new WeChatPayException("sign check fail: check Sign and Data Fail!");
+                }
             }
         }
 

+ 6 - 1
src/Essensoft.Paylink.WeChatPay/V3/WeChatPayPlatformCertificate.cs

@@ -28,6 +28,11 @@ namespace Essensoft.Paylink.WeChatPay.V3
         /// <summary>
         /// 证书
         /// </summary>
-        public X509Certificate2 Certificate;
+        public X509Certificate2 Certificate { get; set; }
+
+        /// <summary>
+        /// 公钥
+        /// </summary>
+        public string PublicKey { get; set; }
     }
 }

+ 4 - 1
src/Essensoft.Paylink.WeChatPay/V3/WeChatPayPlatformCertificateManager.cs

@@ -44,13 +44,16 @@ namespace Essensoft.Paylink.WeChatPay.V3
                                 {
                                     var certStr = AEAD_AES_256_GCM.Decrypt(certificate.EncryptCertificate.Nonce, certificate.EncryptCertificate.Ciphertext, certificate.EncryptCertificate.AssociatedData, options.APIv3Key);
 
+                                    var x509cert = new X509Certificate2(Encoding.ASCII.GetBytes(certStr), string.Empty, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
+
                                     var cert = new WeChatPayPlatformCertificate
                                     {
                                         MchId = options.MchId,
                                         SerialNo = certificate.SerialNo,
                                         EffectiveTime = DateTime.Parse(certificate.EffectiveTime),
                                         ExpireTime = DateTime.Parse(certificate.ExpireTime),
-                                        Certificate = new X509Certificate2(Encoding.ASCII.GetBytes(certStr), string.Empty, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable)
+                                        Certificate = x509cert,
+                                        PublicKey = Convert.ToBase64String(x509cert.GetRSAPublicKey().ExportSubjectPublicKeyInfo())
                                     };
 
                                     _certs.TryAdd(certificate.SerialNo, cert);

+ 17 - 29
src/Essensoft.Paylink.WeChatPay/WeChatPayOptions.cs

@@ -13,7 +13,6 @@ namespace Essensoft.Paylink.WeChatPay
         private string mchId;
         private string certificate;
         private string certificatePassword;
-        private string privateKey;
 
         /// <summary>
         /// 应用号
@@ -104,27 +103,6 @@ namespace Essensoft.Paylink.WeChatPay
             }
         }
 
-        /// <summary>
-        /// 商户API私钥
-        /// </summary>
-        /// <remarks>
-        /// 当配置了P12格式证书时,已包含私钥信息,不必再配置API私钥。PEM格式则必须配置。
-        /// </remarks>
-        public string APIPrivateKey
-        {
-            get => privateKey;
-            set
-            {
-                if (!string.IsNullOrEmpty(value))
-                {
-                    privateKey = value;
-                    var rsa = RSA.Create();
-                    rsa.ImportPkcs8PrivateKey(Convert.FromBase64String(privateKey), out var _);
-                    RSAPrivateKey = rsa;
-                }
-            }
-        }
-
         /// <summary>
         /// 商户API密钥
         /// </summary>
@@ -144,7 +122,7 @@ namespace Essensoft.Paylink.WeChatPay
         public string RsaPublicKey { get; set; }
 
         internal X509Certificate2 Certificate2;
-        internal RSA RSAPrivateKey;
+        internal string RSAPrivateKey;
         internal string CertificateSerialNo;
 
         private void GetCertificateInfo()
@@ -164,18 +142,28 @@ namespace Essensoft.Paylink.WeChatPay
                 {
                     Certificate2 = new X509Certificate2(Convert.FromBase64String(Certificate), CertificatePassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
                 }
+
+                CertificateSerialNo = Certificate2.GetSerialNumberString();
+                var rsaPrivateKey = Certificate2.GetRSAPrivateKey();
+                if (rsaPrivateKey != null)
+                {
+                    RSAPrivateKey = Convert.ToBase64String(rsaPrivateKey.ExportRSAPrivateKey());
+                }
             }
             catch (CryptographicException ex)
             {
                 throw new WeChatPayException($"反序列化证书失败,请确认是否为微信支付签发的有效PKCS#12格式证书。原始异常信息:{ex.Message}");
             }
+        }
 
-            CertificateSerialNo = Certificate2.GetSerialNumberString();
+        /// <summary>
+        /// 微信支付公钥Id
+        /// </summary>
+        public string WeChatPayPublicKeyId { get; set; }
 
-            if (RSAPrivateKey == null)
-            {
-                RSAPrivateKey = Certificate2.GetRSAPrivateKey();
-            }
-        }
+        /// <summary>
+        /// 微信支付公钥
+        /// </summary>
+        public string WeChatPayPublicKey { get; set; }
     }
 }