diff --git a/Samples/All/Senparc.Weixin.Sample.CommonService/Senparc.Weixin.Net8Sample.CommonService.csproj b/Samples/All/Senparc.Weixin.Sample.CommonService/Senparc.Weixin.Net8Sample.CommonService.csproj index 92de20afdd..462a208942 100644 --- a/Samples/All/Senparc.Weixin.Sample.CommonService/Senparc.Weixin.Net8Sample.CommonService.csproj +++ b/Samples/All/Senparc.Weixin.Sample.CommonService/Senparc.Weixin.Net8Sample.CommonService.csproj @@ -18,7 +18,7 @@ - + diff --git a/Samples/All/net8-mvc/Senparc.Weixin.Sample.Net8/Senparc.Weixin.Sample.net8.csproj b/Samples/All/net8-mvc/Senparc.Weixin.Sample.Net8/Senparc.Weixin.Sample.net8.csproj index 6da075e9cc..9ce0316929 100644 --- a/Samples/All/net8-mvc/Senparc.Weixin.Sample.Net8/Senparc.Weixin.Sample.net8.csproj +++ b/Samples/All/net8-mvc/Senparc.Weixin.Sample.Net8/Senparc.Weixin.Sample.net8.csproj @@ -20,9 +20,9 @@ - - - + + + diff --git a/src/Senparc.WebSocket/src/Senparc.WebSocket/Senparc.WebSocket/Senparc.WebSocket.net8.csproj b/src/Senparc.WebSocket/src/Senparc.WebSocket/Senparc.WebSocket/Senparc.WebSocket.net8.csproj index 26c017ce81..fa88a22324 100644 --- a/src/Senparc.WebSocket/src/Senparc.WebSocket/Senparc.WebSocket/Senparc.WebSocket.net8.csproj +++ b/src/Senparc.WebSocket/src/Senparc.WebSocket/Senparc.WebSocket/Senparc.WebSocket.net8.csproj @@ -1,7 +1,7 @@ net462;netstandard2.0;netstandard2.1;netcoreapp3.1;net8.0 - 1.1.5 + 1.1.6 Senparc.WebSocket Senparc.WebSocket true @@ -104,17 +104,12 @@ - - - - - - + diff --git a/src/Senparc.Weixin.All/Senparc.Weixin.All.csproj b/src/Senparc.Weixin.All/Senparc.Weixin.All.csproj index 1abd767162..7dcc6bb983 100644 --- a/src/Senparc.Weixin.All/Senparc.Weixin.All.csproj +++ b/src/Senparc.Weixin.All/Senparc.Weixin.All.csproj @@ -3,7 +3,7 @@ net8.0 enable enable - 2024.11.19 + 2024.11.29 10.0 Senparc.Weixin.All Senparc.Weixin.All diff --git a/src/Senparc.Weixin.AspNet/Senparc.Weixin.AspNet.net8.csproj b/src/Senparc.Weixin.AspNet/Senparc.Weixin.AspNet.net8.csproj index 9ad5496fd1..97f062a554 100644 --- a/src/Senparc.Weixin.AspNet/Senparc.Weixin.AspNet.net8.csproj +++ b/src/Senparc.Weixin.AspNet/Senparc.Weixin.AspNet.net8.csproj @@ -1,7 +1,7 @@ net462;netstandard2.0;netstandard2.1 - 1.4.6 + 1.4.7 Senparc.Weixin.AspNet Senparc.Weixin.AspNet @@ -74,7 +74,7 @@ - + diff --git a/src/Senparc.Weixin.Cache/Senparc.Weixin.Cache.CsRedis/Senparc.Weixin.Cache.CsRedis.net8.csproj b/src/Senparc.Weixin.Cache/Senparc.Weixin.Cache.CsRedis/Senparc.Weixin.Cache.CsRedis.net8.csproj index 7153cf9006..c959fd42d5 100644 --- a/src/Senparc.Weixin.Cache/Senparc.Weixin.Cache.CsRedis/Senparc.Weixin.Cache.CsRedis.net8.csproj +++ b/src/Senparc.Weixin.Cache/Senparc.Weixin.Cache.CsRedis/Senparc.Weixin.Cache.CsRedis.net8.csproj @@ -1,7 +1,7 @@ net462;netstandard2.0;netstandard2.1 - 1.1.4 + 1.1.5 Senparc.Weixin.Cache.CsRedis Senparc.Weixin.Cache.CsRedis true @@ -61,7 +61,7 @@ - + diff --git a/src/Senparc.Weixin.Cache/Senparc.Weixin.Cache.Dapr/Senparc.Weixin.Cache.Dapr.net8.csproj b/src/Senparc.Weixin.Cache/Senparc.Weixin.Cache.Dapr/Senparc.Weixin.Cache.Dapr.net8.csproj index e8ddd769bf..3c5517bec1 100644 --- a/src/Senparc.Weixin.Cache/Senparc.Weixin.Cache.Dapr/Senparc.Weixin.Cache.Dapr.net8.csproj +++ b/src/Senparc.Weixin.Cache/Senparc.Weixin.Cache.Dapr/Senparc.Weixin.Cache.Dapr.net8.csproj @@ -1,7 +1,7 @@ net8.0 - 0.2.4-beta1 + 0.2.5-beta1 Senparc.Weixin.Cache.Dapr Senparc.Weixin.Cache.Dapr true @@ -56,7 +56,7 @@ - + diff --git a/src/Senparc.Weixin.Cache/Senparc.Weixin.Cache.Memcached/Senparc.Weixin.Cache.Memcached.net8.csproj b/src/Senparc.Weixin.Cache/Senparc.Weixin.Cache.Memcached/Senparc.Weixin.Cache.Memcached.net8.csproj index 209ace594d..c2af0a2ded 100644 --- a/src/Senparc.Weixin.Cache/Senparc.Weixin.Cache.Memcached/Senparc.Weixin.Cache.Memcached.net8.csproj +++ b/src/Senparc.Weixin.Cache/Senparc.Weixin.Cache.Memcached/Senparc.Weixin.Cache.Memcached.net8.csproj @@ -1,7 +1,7 @@ net462;netstandard2.0;netstandard2.1 - 2.18.5 + 2.18.6 Senparc.Weixin.Cache.Memcached Senparc.Weixin.Cache.Memcached @@ -143,7 +143,7 @@ - + diff --git a/src/Senparc.Weixin.Cache/Senparc.Weixin.Cache.Redis/Senparc.Weixin.Cache.Redis.net8.csproj b/src/Senparc.Weixin.Cache/Senparc.Weixin.Cache.Redis/Senparc.Weixin.Cache.Redis.net8.csproj index 8d86cb1305..810efaa7d9 100644 --- a/src/Senparc.Weixin.Cache/Senparc.Weixin.Cache.Redis/Senparc.Weixin.Cache.Redis.net8.csproj +++ b/src/Senparc.Weixin.Cache/Senparc.Weixin.Cache.Redis/Senparc.Weixin.Cache.Redis.net8.csproj @@ -1,7 +1,7 @@ net462;netstandard2.0;netstandard2.1 - 2.20.5 + 2.20.6 Senparc.Weixin.Cache.Redis Senparc.Weixin.Cache.Redis true @@ -133,7 +133,7 @@ - + diff --git a/src/Senparc.Weixin.MP.Middleware/Senparc.Weixin.MP.Middleware.net8.csproj b/src/Senparc.Weixin.MP.Middleware/Senparc.Weixin.MP.Middleware.net8.csproj index 193ba75df2..c7cbf7f307 100644 --- a/src/Senparc.Weixin.MP.Middleware/Senparc.Weixin.MP.Middleware.net8.csproj +++ b/src/Senparc.Weixin.MP.Middleware/Senparc.Weixin.MP.Middleware.net8.csproj @@ -1,7 +1,7 @@ net462;netstandard2.0;netstandard2.1;netcoreapp3.1;net8.0 - 1.4.6 + 1.4.7 Senparc.Weixin.MP.Middleware Senparc.Weixin.MP.Middleware true @@ -72,7 +72,7 @@ - + diff --git a/src/Senparc.Weixin.MP.MvcExtension/Senparc.Weixin.MP.MvcExtension/Senparc.Weixin.MP.MvcExtension.net8.csproj b/src/Senparc.Weixin.MP.MvcExtension/Senparc.Weixin.MP.MvcExtension/Senparc.Weixin.MP.MvcExtension.net8.csproj index a33c4a3784..e61b42ad11 100644 --- a/src/Senparc.Weixin.MP.MvcExtension/Senparc.Weixin.MP.MvcExtension/Senparc.Weixin.MP.MvcExtension.net8.csproj +++ b/src/Senparc.Weixin.MP.MvcExtension/Senparc.Weixin.MP.MvcExtension/Senparc.Weixin.MP.MvcExtension.net8.csproj @@ -1,7 +1,7 @@ net462;netstandard2.0;netstandard2.1;netcoreapp3.1;net8.0 - 7.16.6 + 7.16.7 Senparc.Weixin.MP.MvcExtension Senparc.Weixin.MP.MvcExtension true @@ -148,7 +148,7 @@ - + diff --git a/src/Senparc.Weixin.MP/Senparc.Weixin.MP/AdvancedAPIs/Media/MediaApi.cs b/src/Senparc.Weixin.MP/Senparc.Weixin.MP/AdvancedAPIs/Media/MediaApi.cs index 068fc07b2a..100141d473 100644 --- a/src/Senparc.Weixin.MP/Senparc.Weixin.MP/AdvancedAPIs/Media/MediaApi.cs +++ b/src/Senparc.Weixin.MP/Senparc.Weixin.MP/AdvancedAPIs/Media/MediaApi.cs @@ -590,7 +590,7 @@ public static async Task UploadTemporaryMediaAsync(s var url = string.Format(Config.ApiMpHost + "/cgi-bin/media/upload?access_token={0}&type={1}", accessToken.AsUrlData(), type.ToString().AsUrlData()); var fileDictionary = new Dictionary(); fileDictionary["media"] = file; - return await CO2NET.HttpUtility.Post.PostFileGetJsonAsync(CommonDI.CommonSP, url, null, fileDictionary, null, null, null, false, timeOut: timeOut).ConfigureAwait(false); + return await CO2NET.HttpUtility.Post.PostFileGetJsonAsync(CommonDI.CommonSP, url, null, fileDictionary, timeOut: timeOut).ConfigureAwait(false); }, accessTokenOrAppId).ConfigureAwait(false); } diff --git a/src/Senparc.Weixin.MP/Senparc.Weixin.MP/AdvancedAPIs/OAuth/OAuthJson/OAuthAccessTokenResult.cs b/src/Senparc.Weixin.MP/Senparc.Weixin.MP/AdvancedAPIs/OAuth/OAuthJson/OAuthAccessTokenResult.cs index 3454c88afe..c9d440f111 100644 --- a/src/Senparc.Weixin.MP/Senparc.Weixin.MP/AdvancedAPIs/OAuth/OAuthJson/OAuthAccessTokenResult.cs +++ b/src/Senparc.Weixin.MP/Senparc.Weixin.MP/AdvancedAPIs/OAuth/OAuthJson/OAuthAccessTokenResult.cs @@ -41,6 +41,8 @@ and limitations under the License. 修改标识:Senparc - 20220910 修改说明:v16.18.6 OAuth 的 AccessToken 获取接口添加 is_snapshotuser 返回值 + + ----------------------------------------------------------------*/ using System; diff --git a/src/Senparc.Weixin.MP/Senparc.Weixin.MP/Senparc.Weixin.MP.net8.csproj b/src/Senparc.Weixin.MP/Senparc.Weixin.MP/Senparc.Weixin.MP.net8.csproj index 114c42980e..2151f6020a 100644 --- a/src/Senparc.Weixin.MP/Senparc.Weixin.MP/Senparc.Weixin.MP.net8.csproj +++ b/src/Senparc.Weixin.MP/Senparc.Weixin.MP/Senparc.Weixin.MP.net8.csproj @@ -1,7 +1,7 @@ net462;netstandard2.0;netstandard2.1 - 16.23.5 + 16.23.6 Senparc.Weixin.MP Senparc.Weixin.MP true @@ -576,9 +576,9 @@ - - - + + + diff --git a/src/Senparc.Weixin.Open/Senparc.Weixin.Open/OAuthAPIs/OAuthJson/OAuthAccessTokenResult.cs b/src/Senparc.Weixin.Open/Senparc.Weixin.Open/OAuthAPIs/OAuthJson/OAuthAccessTokenResult.cs index 48ef5c0fa0..942d54a729 100644 --- a/src/Senparc.Weixin.Open/Senparc.Weixin.Open/OAuthAPIs/OAuthJson/OAuthAccessTokenResult.cs +++ b/src/Senparc.Weixin.Open/Senparc.Weixin.Open/OAuthAPIs/OAuthJson/OAuthAccessTokenResult.cs @@ -9,6 +9,9 @@ 修改标识:Senparc - 20161216 修改描述:v2.3.5 添加序列化特性 + + 修改标识:Senparc - 20241129 + 修改描述:v4.21.8 返回值添加参数 is_snapshotuser 和 unionid ----------------------------------------------------------------*/ @@ -44,5 +47,13 @@ public class OAuthAccessTokenResult : WxJsonResult /// 用户授权的作用域,使用逗号(,)分隔 /// public string scope { get; set; } + /// + /// 用户统一标识(针对一个微信开放平台账号下的应用,同一用户的 unionid 是唯一的),只有当scope为"snsapi_userinfo"时返回 + /// + public string unionid { get; set; } + /// + /// 是否为快照页模式虚拟账号,只有当用户是快照页模式虚拟账号是返回,值为1 + /// + public int? is_snapshotuser { get; set; } } } diff --git a/src/Senparc.Weixin.Open/Senparc.Weixin.Open/Senparc.Weixin.Open.net8.csproj b/src/Senparc.Weixin.Open/Senparc.Weixin.Open/Senparc.Weixin.Open.net8.csproj index 04549d48c1..7b119dd4c4 100644 --- a/src/Senparc.Weixin.Open/Senparc.Weixin.Open/Senparc.Weixin.Open.net8.csproj +++ b/src/Senparc.Weixin.Open/Senparc.Weixin.Open/Senparc.Weixin.Open.net8.csproj @@ -1,7 +1,7 @@ net462;netstandard2.0;netstandard2.1 - 4.21.7 + 4.21.9 Senparc.Weixin.Open Senparc.Weixin.Open true @@ -227,6 +227,7 @@ [2024-08-11] v4.20.0.0 1、添加查询小程序是否已完成交易结算管理确认 2、添加第三方小程序订单页设置结果及审核结果事件通知 #3055 感谢 @mc7246 [2024-09-10] v4.20.2 icp verifytask 接口 data 不能为 null 的问题处理 #3067 感谢 @mojinxun [2024-11-03] v4.20.6 fix wxa ap is get qr code async Issue #3089, PR #3090 感谢 @JaneConan + [2024-11-29] v4.21.8 返回值添加参数 is_snapshotuser 和 unionid #3100 感谢 @ccccccmd https://github.com/JeffreySu/WeiXinMPSDK @@ -270,7 +271,7 @@ - + diff --git a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPay/Senparc.Weixin.TenPay.net8.csproj b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPay/Senparc.Weixin.TenPay.net8.csproj index 633ea6672b..a4cd9e4231 100644 --- a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPay/Senparc.Weixin.TenPay.net8.csproj +++ b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPay/Senparc.Weixin.TenPay.net8.csproj @@ -1,7 +1,7 @@ net462;netstandard2.0;netstandard2.1 - 1.17.5 + 1.17.6 Senparc.Weixin.TenPay Senparc.Weixin.TenPay true @@ -97,7 +97,7 @@ - + diff --git a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3.Test/Apis/Profitsharing/ProfitsharingApisTest.cs b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3.Test/Apis/Profitsharing/ProfitsharingApisTest.cs index db21f9914d..625da8f556 100644 --- a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3.Test/Apis/Profitsharing/ProfitsharingApisTest.cs +++ b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3.Test/Apis/Profitsharing/ProfitsharingApisTest.cs @@ -12,6 +12,7 @@ namespace Senparc.Weixin.TenPayV3.Test.net6.Apis { + [TestClass()] public class ProfitsharingApisTest : BaseTenPayTest { #region 分账接口 diff --git a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3.Test/Senparc.Weixin.TenPayV3.Test.net8.csproj b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3.Test/Senparc.Weixin.TenPayV3.Test.net8.csproj index 0b00522295..e9b023f2fc 100644 --- a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3.Test/Senparc.Weixin.TenPayV3.Test.net8.csproj +++ b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3.Test/Senparc.Weixin.TenPayV3.Test.net8.csproj @@ -18,11 +18,6 @@ true PreserveNewest - - PreserveNewest - true - PreserveNewest - PreserveNewest true diff --git a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3.Test/appsettings.json b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3.Test/appsettings.json index d5cecc784d..2dc4d0f689 100644 --- a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3.Test/appsettings.json +++ b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3.Test/appsettings.json @@ -72,6 +72,8 @@ "TenPayV3_PrivateKey": "#{TenPayV3_PrivateKey}#", //(新)证书私钥 "TenPayV3_SerialNumber": "#{TenPayV3_SerialNumber}#", //(新)证书序列号 "TenPayV3_ApiV3Key": "#{TenPayV3_APIv3Key}#", //(新)APIv3 密钥 + "TenPayV3_TenPayPubKey": "#{TenPayV3_TenPayPubKey}#", // (新)微信支付公钥证书-验证微信支付身份,支持本地项目路径/字符串,空则代表使用原来的平台证书,如:D:\\cert\\cert.pem + "TenPayV3_TenPayPubKeyID": "#{TenPayV3_TenPayPubKeyID}#", // (新)微信支付公钥ID-验证微信支付身份,空则代表使用原来的平台证书,如PUB_KEY_ID_0000000000000000000000 //如果不设置TenPayV3_WxOpenTenpayNotify,默认在 TenPayV3_TenpayNotify 的值最后加上 "WxOpen" "TenPayV3_WxOpenTenpayNotify": "#{TenPayV3_WxOpenTenpayNotify}#", //http://YourDomainName/TenpayV3/PayNotifyUrlWxOpen //开放平台 diff --git a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Apis/BasePay/BasePayApis.cs b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Apis/BasePay/BasePayApis.cs index 627e5ef2f1..b049924766 100644 --- a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Apis/BasePay/BasePayApis.cs +++ b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Apis/BasePay/BasePayApis.cs @@ -118,7 +118,7 @@ internal static string GetPayApiUrl(string urlFormat, string sp_mchid = "") /// public async Task CertificatesAsync(string algorithmType = "RSA", int timeOut = Config.TIME_OUT) { - var url = BasePayApis.GetPayApiUrl(Senparc.Weixin.Config.TenPayV3Host + "/{0}v3/certificates?algorithm_type=" + algorithmType); + var url = GetPayApiUrl(Senparc.Weixin.Config.TenPayV3Host + "/{0}v3/certificates?algorithm_type=" + algorithmType); TenPayApiRequest tenPayApiRequest = new(_tenpayV3Setting); //var responseMessge = await tenPayApiRequest.GetHttpResponseMessageAsync(url, null, timeOut); //return await responseMessge.Content.ReadAsStringAsync(); @@ -132,7 +132,15 @@ public async Task CertificatesAsync(string algorithmType /// public async Task GetPublicKeysAsync(/*int timeOut = Config.TIME_OUT*/) { - string algorithmType = _tenpayV3Setting.EncryptionType == CertType.SM.ToString() ? "SM2" : "RSA"; + PublicKeyCollection keys = new(); + + if (_tenpayV3Setting.TenPayV3_TenPayPubKeyEnable) + { + keys[_tenpayV3Setting.TenPayV3_TenPayPubKeyID] = SecurityHelper.GetUnwrapCertKey(_tenpayV3Setting.TenPayV3_TenPayPubKey); + return keys; + } + + var algorithmType = _tenpayV3Setting.EncryptionType == CertType.SM.ToString() ? "SM2" : "RSA"; var certificates = await CertificatesAsync(algorithmType); if (!certificates.ResultCode.Success) { @@ -144,12 +152,11 @@ public async Task GetPublicKeysAsync(/*int timeOut = Config throw new TenpayApiRequestException("Certificates 获取结果为空"); } - PublicKeyCollection keys = new(); //var tenpayV3Setting = Senparc.Weixin.Config.SenparcWeixinSetting.TenpayV3Setting;//TODO:改成从构造函数配置 foreach (var cert in certificates.data) { - if(cert.encrypt_certificate.algorithm == "AEAD_AES_256_GCM") + if (cert.encrypt_certificate.algorithm == "AEAD_AES_256_GCM") { var publicKey = SecurityHelper.AesGcmDecryptCiphertext(_tenpayV3Setting.TenPayV3_APIv3Key, cert.encrypt_certificate.nonce, cert.encrypt_certificate.associated_data, cert.encrypt_certificate.ciphertext); diff --git a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Apis/Profitsharing/ProfitsharingApis.cs b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Apis/Profitsharing/ProfitsharingApis.cs index 63b3b7ef4b..e24d7f92db 100644 --- a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Apis/Profitsharing/ProfitsharingApis.cs +++ b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Apis/Profitsharing/ProfitsharingApis.cs @@ -111,18 +111,24 @@ public async Task CreateProfitsharingAsync(Create // name加密 var basePayApis = new BasePayApis(); - - string algorithmType = _tenpayV3Setting.EncryptionType == CertType.SM.ToString() ? "SM2" : "RSA"; - var certificateResponse = await basePayApis.CertificatesAsync(algorithmType); + var publicKeys = await basePayApis.GetPublicKeysAsync(); + var publicKeyKv = publicKeys.FirstOrDefault(); foreach (var each in data.receivers) { - SecurityHelper.FieldEncrypt(each, certificateResponse, _tenpayV3Setting.TenPayV3_APIv3Key, _tenpayV3Setting.EncryptionType); + SecurityHelper.FieldEncrypt(each, publicKeyKv.Value, _tenpayV3Setting.EncryptionType, _tenpayV3Setting.TenPayV3_TenPayPubKeyEnable); } + //string algorithmType = _tenpayV3Setting.EncryptionType == CertType.SM.ToString() ? "SM2" : "RSA"; + //var certificateResponse = await basePayApis.CertificatesAsync(algorithmType); + //foreach (var each in data.receivers) + //{ + // SecurityHelper.FieldEncrypt(each, certificateResponse, _tenpayV3Setting.TenPayV3_APIv3Key, _tenpayV3Setting.EncryptionType); + //} + var url = ReurnPayApiUrl(Senparc.Weixin.Config.TenPayV3Host + "/{0}v3/{1}profitsharing/orders", data.brand_mchid); TenPayApiRequest tenPayApiRequest = new(_tenpayV3Setting, httpClient => { - httpClient.DefaultRequestHeaders.Add("Wechatpay-Serial", certificateResponse.data?.FirstOrDefault()?.serial_no); + httpClient.DefaultRequestHeaders.Add("Wechatpay-Serial", publicKeyKv.Key); }); return await tenPayApiRequest.RequestAsync(url, data, timeOut); } @@ -316,14 +322,18 @@ public async Task AddProfitsharingReceiverAs // name加密 var basePayApis = new BasePayApis(); - string algorithmType = _tenpayV3Setting.EncryptionType == CertType.SM.ToString() ? "SM2" : "RSA"; - var certificateResponse = await basePayApis.CertificatesAsync(algorithmType); - SecurityHelper.FieldEncrypt(data, certificateResponse, _tenpayV3Setting.TenPayV3_APIv3Key, _tenpayV3Setting.EncryptionType); + var publicKeys = await basePayApis.GetPublicKeysAsync(); + var publicKeyKv = publicKeys.FirstOrDefault(); + SecurityHelper.FieldEncrypt(data, publicKeyKv.Value, _tenpayV3Setting.EncryptionType, _tenpayV3Setting.TenPayV3_TenPayPubKeyEnable); + + //string algorithmType = _tenpayV3Setting.EncryptionType == CertType.SM.ToString() ? "SM2" : "RSA"; + //var certificateResponse = await basePayApis.CertificatesAsync(algorithmType); + //SecurityHelper.FieldEncrypt(data, certificateResponse, _tenpayV3Setting.TenPayV3_APIv3Key, _tenpayV3Setting.EncryptionType); var url = ReurnPayApiUrl(Senparc.Weixin.Config.TenPayV3Host + "/{0}v3/{1}profitsharing/receivers/add", data.brand_mchid); TenPayApiRequest tenPayApiRequest = new(_tenpayV3Setting, httpClient => { - httpClient.DefaultRequestHeaders.Add("Wechatpay-Serial", certificateResponse.data?.FirstOrDefault()?.serial_no); + httpClient.DefaultRequestHeaders.Add("Wechatpay-Serial", publicKeyKv.Key); }); return await tenPayApiRequest.RequestAsync(url, data, timeOut); } diff --git a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Enums.cs b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Enums.cs index 67186935cc..6fce1d1bd0 100644 --- a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Enums.cs +++ b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Enums.cs @@ -62,6 +62,9 @@ public enum ApiRequestMethod public enum CertType { RSA, - SM + SM, + + PUBKEY_RSA, + PUBKEY_SM } } diff --git a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Helpers/SecurityHelper.cs b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Helpers/SecurityHelper.cs index fea264bc0f..d371f09efc 100644 --- a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Helpers/SecurityHelper.cs +++ b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Helpers/SecurityHelper.cs @@ -35,6 +35,7 @@ and limitations under the License. using Org.BouncyCastle.Crypto.Engines; using Org.BouncyCastle.Crypto.Modes; using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; using Senparc.CO2NET.Helpers; using Senparc.Weixin.TenPayV3.Apis; using Senparc.Weixin.TenPayV3.Apis.BasePay; @@ -47,6 +48,7 @@ and limitations under the License. using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.Xml; namespace Senparc.Weixin.TenPayV3.Helpers { @@ -76,47 +78,31 @@ public static string GetUnwrapCertKey(string originalPublicKey) } /// - /// 敏感信息加密需要从https://api.mch.weixin.qq.com/risk/getcertficates此接口下载加密证书进行下一步加密, - /// 该接口下载到的是密文,使用此AESGCM.Decrypt()方法解密得到证书明文 + /// 加密敏感信息,传入明文和从微信支付获取到的敏感信息加密公钥,事先使用OpenSSL转换cert.pem文件输出为der文件 /// - /// - /// + /// + /// /// + /// 是否是微信支付公钥 /// - public static string GetPublicKey(Encrypt_Certificate encryptCertificate, string apiV3Key, string encryptionType) + public static string Encrypt(string text, string publicKey, string encryptionType, bool isWeixinPubKey = false) { - if (encryptionType == CertType.SM.ToString()) - { - return GmHelper.Sm4DecryptGCM(apiV3Key, encryptCertificate.nonce, "certificate", encryptCertificate.ciphertext); - } - else + #region 基于微信支付公钥 + if (isWeixinPubKey) { - var buff = Convert.FromBase64String(encryptCertificate.ciphertext); - var secret = Encoding.UTF8.GetBytes(apiV3Key); - var nonce = Encoding.UTF8.GetBytes(encryptCertificate.nonce); - var associatedData = Encoding.UTF8.GetBytes("certificate"); - - // 算法 AEAD_AES_256_GCM,C# 环境使用 BouncyCastle.Crypto.dll 类库实现 - var cipher = new GcmBlockCipher(new AesEngine()); - var aead = new AeadParameters(new KeyParameter(secret), 128, nonce, associatedData); - cipher.Init(false, aead); + var publicKeyParam = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(publicKey)); + var publicKeyXml = string.Format("{0}{1}", + Convert.ToBase64String(publicKeyParam.Modulus.ToByteArrayUnsigned()), + Convert.ToBase64String(publicKeyParam.Exponent.ToByteArrayUnsigned())); - var data = new byte[cipher.GetOutputSize(buff.Length)]; - var num = cipher.ProcessBytes(buff, 0, buff.Length, data, 0); - cipher.DoFinal(data, num); - return Encoding.UTF8.GetString(data); + var rsa = new RSACryptoServiceProvider(); + RSAFromXmlString(rsa, publicKeyXml); + var buff = rsa.Encrypt(Encoding.UTF8.GetBytes(text), RSAEncryptionPadding.OaepSHA1); + return Convert.ToBase64String(buff); } - } + #endregion + - /// - /// 加密敏感信息,传入明文和从微信支付获取到的敏感信息加密公钥,事先使用OpenSSL转换cert.pem文件输出为der文件 - /// - /// - /// - /// - /// - public static string Encrypt(string text, string publicKey, string encryptionType) - { if (encryptionType == CertType.SM.ToString()) { ECPublicKeyParameters eCPublicKeyParameters = SMPemHelper.LoadPublicKeyToParameters(Encoding.UTF8.GetBytes(publicKey)); @@ -137,7 +123,8 @@ public static string Encrypt(string text, string publicKey, string encryptionTyp /// /// /// - public static void FieldEncrypt(object request, string publicKey, string encryptionType) + /// 是否是微信支付公钥 + public static void FieldEncrypt(object request, string publicKey, string encryptionType, bool isWeixinPubKey = false) { var pis = request.GetType().GetProperties(); foreach (var pi in pis) @@ -148,14 +135,14 @@ public static void FieldEncrypt(object request, string publicKey, string encrypt if (!(pi.PropertyType.IsValueType || pi.PropertyType.Name.StartsWith("String") || typeof(IEnumerable).IsAssignableFrom(pi.PropertyType))) { - FieldEncrypt(value, publicKey, encryptionType); + FieldEncrypt(value, publicKey, encryptionType, isWeixinPubKey); continue; } if ((pi.GetCustomAttributes(typeof(FieldEncryptAttribute), true)?.Count() ?? 0) <= 0) continue; - var encryptValue = Encrypt(value.ToString(), publicKey, encryptionType); + var encryptValue = Encrypt(value.ToString(), publicKey, encryptionType, isWeixinPubKey); pi.SetValue(request, encryptValue); } } @@ -168,6 +155,24 @@ public static void FieldEncrypt(object request, string publicKey, string encrypt /// /// /// + [Obsolete("放弃使用")] + public static async Task FieldEncryptAsync(object request, string apiV3Key, string encryptionType) + { + // name 敏感信息加密 + var basePayApis = new BasePayApis(); + var certificate = await basePayApis.CertificatesAsync(); + FieldEncrypt(request, certificate, apiV3Key, encryptionType); + } + + /// + /// 字段加密 + /// + /// + /// + /// + /// + /// + [Obsolete("放弃使用")] public static void FieldEncrypt(object request, CertificatesResultJson certificate, string apiV3Key, string encryptionType) { if (!(certificate?.ResultCode?.Success ?? false)) @@ -179,19 +184,99 @@ public static void FieldEncrypt(object request, CertificatesResultJson certifica } /// - /// 字段加密 + /// 敏感信息加密需要从https://api.mch.weixin.qq.com/risk/getcertficates此接口下载加密证书进行下一步加密, + /// 该接口下载到的是密文,使用此AESGCM.Decrypt()方法解密得到证书明文 /// - /// + /// /// /// /// - /// - public static async Task FieldEncryptAsync(object request, string apiV3Key, string encryptionType) + public static string GetPublicKey(Encrypt_Certificate encryptCertificate, string apiV3Key, string encryptionType) { - // name 敏感信息加密 - var basePayApis = new BasePayApis(); - var certificate = await basePayApis.CertificatesAsync(); - FieldEncrypt(request, certificate, apiV3Key, encryptionType); + if (encryptionType == CertType.SM.ToString()) + { + return GmHelper.Sm4DecryptGCM(apiV3Key, encryptCertificate.nonce, "certificate", encryptCertificate.ciphertext); + } + else + { + var buff = Convert.FromBase64String(encryptCertificate.ciphertext); + var secret = Encoding.UTF8.GetBytes(apiV3Key); + var nonce = Encoding.UTF8.GetBytes(encryptCertificate.nonce); + var associatedData = Encoding.UTF8.GetBytes("certificate"); + + // 算法 AEAD_AES_256_GCM,C# 环境使用 BouncyCastle.Crypto.dll 类库实现 + var cipher = new GcmBlockCipher(new AesEngine()); + var aead = new AeadParameters(new KeyParameter(secret), 128, nonce, associatedData); + cipher.Init(false, aead); + + var data = new byte[cipher.GetOutputSize(buff.Length)]; + var num = cipher.ProcessBytes(buff, 0, buff.Length, data, 0); + cipher.DoFinal(data, num); + return Encoding.UTF8.GetString(data); + } + } + + public static void RSAFromXmlString(RSA rsa, string xmlString) + { + var parameters = new RSAParameters(); + var xmlDoc = new XmlDocument(); + xmlDoc.LoadXml(xmlString); + + if (xmlDoc.DocumentElement.Name.Equals("RSAKeyValue")) + { + foreach (XmlNode node in xmlDoc.DocumentElement.ChildNodes) + { + switch (node.Name) + { + case "Modulus": + parameters.Modulus = (string.IsNullOrEmpty(node.InnerText) + ? null + : Convert.FromBase64String(node.InnerText)); + break; + case "Exponent": + parameters.Exponent = (string.IsNullOrEmpty(node.InnerText) + ? null + : Convert.FromBase64String(node.InnerText)); + break; + case "P": + parameters.P = (string.IsNullOrEmpty(node.InnerText) + ? null + : Convert.FromBase64String(node.InnerText)); + break; + case "Q": + parameters.Q = (string.IsNullOrEmpty(node.InnerText) + ? null + : Convert.FromBase64String(node.InnerText)); + break; + case "DP": + parameters.DP = (string.IsNullOrEmpty(node.InnerText) + ? null + : Convert.FromBase64String(node.InnerText)); + break; + case "DQ": + parameters.DQ = (string.IsNullOrEmpty(node.InnerText) + ? null + : Convert.FromBase64String(node.InnerText)); + break; + case "InverseQ": + parameters.InverseQ = (string.IsNullOrEmpty(node.InnerText) + ? null + : Convert.FromBase64String(node.InnerText)); + break; + case "D": + parameters.D = (string.IsNullOrEmpty(node.InnerText) + ? null + : Convert.FromBase64String(node.InnerText)); + break; + } + } + } + else + { + throw new Exception("Invalid XML RSA key."); + } + + rsa.ImportParameters(parameters); } } } diff --git a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Helpers/TenPaySignHelper.cs b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Helpers/TenPaySignHelper.cs index 76f3745305..e39dfa07de 100644 --- a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Helpers/TenPaySignHelper.cs +++ b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Helpers/TenPaySignHelper.cs @@ -37,6 +37,7 @@ and limitations under the License. using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; using Senparc.Weixin.Entities; using Senparc.Weixin.Helpers; using System; @@ -127,18 +128,44 @@ public static string CreatePaySign(string timeStamp, string nonceStr, string pac /// HTTP头中的应答随机串 /// HTTP头中的应答签名(Base64) /// 应答报文主体 - /// 平台公钥(必须是Unwrap的公钥) + /// 平台公钥/微信支付公钥(必须是Unwrap的公钥) + /// 是否是微信支付公钥 /// - public static bool VerifyTenpaySign(string wechatpayTimestamp, string wechatpayNonce, string wechatpaySignatureBase64, string content, string pubKey) + public static bool VerifyTenpaySign(string wechatpayTimestamp, string wechatpayNonce, string wechatpaySignatureBase64, string content, string pubKey, bool isTenPayPubKey = false) { //验签名串 - string contentForSign = $"{wechatpayTimestamp}\n{wechatpayNonce}\n{content}\n"; + var contentForSign = $"{wechatpayTimestamp}\n{wechatpayNonce}\n{content}\n"; - if(Senparc.Weixin.Config.SenparcWeixinSetting.EncryptionType == CertType.SM.ToString()) + if (isTenPayPubKey) + { + var publicKeyParam = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(pubKey)); + var publicKeyXml = string.Format("{0}{1}", + Convert.ToBase64String(publicKeyParam.Modulus.ToByteArrayUnsigned()), + Convert.ToBase64String(publicKeyParam.Exponent.ToByteArrayUnsigned())); + + var rsa = new RSACryptoServiceProvider(); + SecurityHelper.RSAFromXmlString(rsa, publicKeyXml); + + //RSAPKCS1SignatureDeformatter 对象 + RSAPKCS1SignatureDeformatter df = new RSAPKCS1SignatureDeformatter(rsa); + //指定 SHA256 + df.SetHashAlgorithm("SHA256"); + //SHA256Managed 方法已弃用,使用 SHA256.Create() 生成 SHA256 对象 + var sha256 = SHA256.Create(); + //应答签名 + byte[] signature = Convert.FromBase64String(wechatpaySignatureBase64); + //对比签名 + byte[] compareByte = sha256.ComputeHash(Encoding.UTF8.GetBytes(contentForSign)); + //验证签名 + var result = df.VerifySignature(compareByte, signature); + return result; + } + + + if (Senparc.Weixin.Config.SenparcWeixinSetting.EncryptionType == CertType.SM.ToString()) { byte[] pubKeyBytes = Convert.FromBase64String(pubKey); ECPublicKeyParameters eCPublicKeyParameters = SMPemHelper.LoadPublicKeyToParameters(pubKeyBytes); - return GmHelper.VerifySm3WithSm2(eCPublicKeyParameters, contentForSign, wechatpaySignatureBase64); } else @@ -184,7 +211,7 @@ public static async Task VerifyTenpaySign(string wechatpayTimestamp, strin var tenpayV3InfoKey = TenPayHelper.GetRegisterKey(senparcWeixinSettingForTenpayV3.TenPayV3_MchId, senparcWeixinSettingForTenpayV3.TenPayV3_SubMchId); var pubKey = await TenPayV3InfoCollection.Data[tenpayV3InfoKey].GetPublicKeyAsync(serialNumber, senparcWeixinSettingForTenpayV3); - return VerifyTenpaySign(wechatpayTimestamp, wechatpayNonce, wechatpaySignature, content, pubKey); + return VerifyTenpaySign(wechatpayTimestamp, wechatpayNonce, wechatpaySignature, content, pubKey, senparcWeixinSettingForTenpayV3.TenPayV3_TenPayPubKeyEnable); } /// diff --git a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/HttpHandlers/TenPayApiRequest.cs b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/HttpHandlers/TenPayApiRequest.cs index d54d243ab2..8c2303aea2 100644 --- a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/HttpHandlers/TenPayApiRequest.cs +++ b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/HttpHandlers/TenPayApiRequest.cs @@ -231,7 +231,7 @@ public async Task RequestAsync(string url, object data, int timeOut = Conf try { var pubKey = await TenPayV3InfoCollection.GetAPIv3PublicKeyAsync(this._tenpayV3Setting, wechatpaySerial); - if(this._tenpayV3Setting.EncryptionType == CertType.SM.ToString()) + if (this._tenpayV3Setting.EncryptionType == CertType.SM.ToString()) { byte[] pubKeyBytes = Convert.FromBase64String(pubKey); ECPublicKeyParameters eCPublicKeyParameters = SMPemHelper.LoadPublicKeyToParameters(pubKeyBytes); @@ -243,7 +243,7 @@ public async Task RequestAsync(string url, object data, int timeOut = Conf } else { - result.VerifySignSuccess = TenPaySignHelper.VerifyTenpaySign(wechatpayTimestamp, wechatpayNonce, wechatpaySignatureBase64, content, pubKey); + result.VerifySignSuccess = TenPaySignHelper.VerifyTenpaySign(wechatpayTimestamp, wechatpayNonce, wechatpaySignatureBase64, content, pubKey, _tenpayV3Setting.TenPayV3_TenPayPubKeyEnable); } } catch (Exception ex) diff --git a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Senparc.Weixin.TenPayV3.net8.csproj b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Senparc.Weixin.TenPayV3.net8.csproj index 029ecd8adc..9a133150ba 100644 --- a/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Senparc.Weixin.TenPayV3.net8.csproj +++ b/src/Senparc.Weixin.TenPay/Senparc.Weixin.TenPayV3/Senparc.Weixin.TenPayV3.net8.csproj @@ -1,7 +1,7 @@ netstandard2.1 - 1.7.7 + 1.7.8 Senparc.Weixin.TenPayV3 Senparc.Weixin.TenPayV3 10.0 @@ -58,7 +58,8 @@ v1.4.2 完善 SM 相关方法 v1.6.3 更新支付接口调用过程中的 SM 和 RSA 判断方式 v1.6.5 修改 SM 证书判断逻辑,向下兼容未升级 appsettings.json 的系统 #3084 - + v1.7.8 兼容微信支付公钥以及平台证书 / PR #3103 感谢 @mojinxun + https://github.com/JeffreySu/WeiXinMPSDK @@ -90,7 +91,7 @@ - + diff --git a/src/Senparc.Weixin.Work.Middleware/Senparc.Weixin.Work.Middleware.net8.csproj b/src/Senparc.Weixin.Work.Middleware/Senparc.Weixin.Work.Middleware.net8.csproj index 3cf9e470f1..0f0bc509a8 100644 --- a/src/Senparc.Weixin.Work.Middleware/Senparc.Weixin.Work.Middleware.net8.csproj +++ b/src/Senparc.Weixin.Work.Middleware/Senparc.Weixin.Work.Middleware.net8.csproj @@ -1,7 +1,7 @@ net462;netstandard2.0;netstandard2.1;netcoreapp3.1;net8.0 - 1.4.6 + 1.4.7 Senparc.Weixin.Work.Middleware Senparc.Weixin.Work.Middleware true @@ -69,7 +69,7 @@ - + diff --git a/src/Senparc.Weixin.Work/Senparc.Weixin.Work/AdvancedAPIs/Media/MediaApi.cs b/src/Senparc.Weixin.Work/Senparc.Weixin.Work/AdvancedAPIs/Media/MediaApi.cs index 27b025b989..0b070057bc 100644 --- a/src/Senparc.Weixin.Work/Senparc.Weixin.Work/AdvancedAPIs/Media/MediaApi.cs +++ b/src/Senparc.Weixin.Work/Senparc.Weixin.Work/AdvancedAPIs/Media/MediaApi.cs @@ -71,10 +71,8 @@ public static UploadTemporaryResultJson Upload(string accessTokenOrAppKey, Uploa var url = string.Format(Config.ApiWorkHost + "/cgi-bin/media/upload?access_token={0}&type={1}", accessToken.AsUrlData(), type.ToString()); var fileDictionary = new Dictionary(); fileDictionary["media"] = media; - return CO2NET.HttpUtility.Post.PostFileGetJson(CommonDI.CommonSP, url, null, fileDictionary, null, null, null, false, timeOut: timeOut); + return CO2NET.HttpUtility.Post.PostFileGetJson(CommonDI.CommonSP, url, null, fileDictionary, timeOut: timeOut); }, accessTokenOrAppKey); - - } /// @@ -96,9 +94,6 @@ public static void Get(string accessTokenOrAppKey, string mediaId, Stream stream return new WorkJsonResult() { errcode = ReturnCode_Work.请求成功, errmsg = "ok" }; }, accessTokenOrAppKey); - - - } /// /// 获取临时媒体文件并保存到指定目录中 @@ -171,8 +166,6 @@ public static UploadForeverResultJson AddMpNews(string accessTokenOrAppKey, int return CommonJsonSend.Send(null, url, data, CommonJsonSendType.POST, timeOut); }, accessTokenOrAppKey); - - } /// @@ -191,10 +184,8 @@ public static UploadForeverResultJson AddMaterial(string accessTokenOrAppKey, Up var url = string.Format(Config.ApiWorkHost + "/cgi-bin/material/add_material?agentid={1}&type={2}&access_token={0}", accessToken.AsUrlData(), agentId, type); var fileDictionary = new Dictionary(); fileDictionary["media"] = media; - return Post.PostFileGetJson(CommonDI.CommonSP, url, null, fileDictionary, null, null, null, false, null, timeOut: timeOut); + return Post.PostFileGetJson(CommonDI.CommonSP, url, null, fileDictionary, null, null, null, timeOut: timeOut); }, accessTokenOrAppKey); - - } /// @@ -215,8 +206,6 @@ public static GetForeverMpNewsResult GetForeverMpNews(string accessTokenOrAppKey return CommonJsonSend.Send(null, url, null, CommonJsonSendType.GET); }, accessTokenOrAppKey); - - } /// @@ -259,8 +248,6 @@ public static WorkJsonResult DeleteForeverMaterial(string accessTokenOrAppKey, i return CommonJsonSend.Send(null, url, null, CommonJsonSendType.GET); }, accessTokenOrAppKey); - - } /// @@ -291,8 +278,6 @@ public static UploadForeverResultJson UpdateMpNews(string accessTokenOrAppKey, s return CommonJsonSend.Send(null, url, data, CommonJsonSendType.POST, timeOut); }, accessTokenOrAppKey); - - } /// @@ -310,8 +295,6 @@ public static GetCountResult GetCount(string accessTokenOrAppKey, int agentId) return CommonJsonSend.Send(null, url, null, CommonJsonSendType.GET); }, accessTokenOrAppKey); - - } /// @@ -341,9 +324,8 @@ public static BatchGetMaterialResult BatchGetMaterial(string accessTokenOrAppKey return CommonJsonSend.Send(null, url, data, CommonJsonSendType.POST, timeOut); }, accessTokenOrAppKey); - - } + /// /// 上传图文消息内的图片 /// 上传的图片限制:大小不超过2MB,支持JPG,PNG格式,每天上传的图片不能超过100张 @@ -365,8 +347,6 @@ public static UploadimgMediaResult UploadimgMedia(string accessTokenOrAppKey, st return Post.PostFileGetJson(CommonDI.CommonSP, url, null, fileDictionary, null, timeOut: timeOut); }, accessTokenOrAppKey); - - } #endregion @@ -388,10 +368,8 @@ public static async Task UploadAsync(string accessTok var url = string.Format(Config.ApiWorkHost + "/cgi-bin/media/upload?access_token={0}&type={1}", accessToken.AsUrlData(), type.ToString()); var fileDictionary = new Dictionary(); fileDictionary["media"] = media; - return await Post.PostFileGetJsonAsync(CommonDI.CommonSP, url, null, fileDictionary, null, null, null, false, null, timeOut: timeOut).ConfigureAwait(false); + return await Post.PostFileGetJsonAsync(CommonDI.CommonSP, url, null, fileDictionary, timeOut: timeOut).ConfigureAwait(false); }, accessTokenOrAppKey).ConfigureAwait(false); - - } /// @@ -410,8 +388,6 @@ await ApiHandlerWapper.TryCommonApiAsync(async accessToken => await CO2NET.HttpUtility.Get.DownloadAsync(CommonDI.CommonSP, url, stream).ConfigureAwait(false);//todo 异常处理 return new WorkJsonResult() { errcode = ReturnCode_Work.请求成功, errmsg = "ok" }; }, accessTokenOrAppKey).ConfigureAwait(false); - - } /// @@ -440,8 +416,6 @@ public static async Task AddMpNewsAsync(string accessTo return await Senparc.Weixin.CommonAPIs.CommonJsonSend.SendAsync(null, url, data, CommonJsonSendType.POST, timeOut).ConfigureAwait(false); }, accessTokenOrAppKey).ConfigureAwait(false); - - } /// @@ -460,10 +434,8 @@ public static async Task AddMaterialAsync(string access var url = string.Format(Config.ApiWorkHost + "/cgi-bin/material/add_material?agentid={1}&type={2}&access_token={0}", accessToken.AsUrlData(), agentId, type); var fileDictionary = new Dictionary(); fileDictionary["media"] = media; - return await Post.PostFileGetJsonAsync(CommonDI.CommonSP, url, null, fileDictionary, null, null, null, false, null, timeOut: timeOut).ConfigureAwait(false); + return await Post.PostFileGetJsonAsync(CommonDI.CommonSP, url, null, fileDictionary, timeOut: timeOut).ConfigureAwait(false); }, accessTokenOrAppKey).ConfigureAwait(false); - - } /// @@ -484,7 +456,6 @@ public static async Task GetForeverMpNewsAsync(string ac return await Senparc.Weixin.CommonAPIs.CommonJsonSend.SendAsync(null, url, null, CommonJsonSendType.GET).ConfigureAwait(false); }, accessTokenOrAppKey).ConfigureAwait(false); - } /// @@ -508,8 +479,6 @@ await ApiHandlerWapper.TryCommonApiAsync(async accessToken => return new WorkJsonResult() { errcode = ReturnCode_Work.请求成功, errmsg = "ok" }; }, accessTokenOrAppKey).ConfigureAwait(false); - - } /// @@ -530,8 +499,6 @@ public static async Task DeleteForeverMaterialAsync(string acces return await Senparc.Weixin.CommonAPIs.CommonJsonSend.SendAsync(null, url, null, CommonJsonSendType.GET).ConfigureAwait(false); }, accessTokenOrAppKey).ConfigureAwait(false); - - } /// @@ -562,8 +529,6 @@ public static async Task UpdateMpNewsAsync(string acces return await Senparc.Weixin.CommonAPIs.CommonJsonSend.SendAsync(null, url, data, CommonJsonSendType.POST, timeOut).ConfigureAwait(false); }, accessTokenOrAppKey).ConfigureAwait(false); - - } /// @@ -581,8 +546,6 @@ public static async Task GetCountAsync(string accessTokenOrAppKe return await Senparc.Weixin.CommonAPIs.CommonJsonSend.SendAsync(null, url, null, CommonJsonSendType.GET).ConfigureAwait(false); }, accessTokenOrAppKey).ConfigureAwait(false); - - } /// @@ -612,8 +575,6 @@ public static async Task BatchGetMaterialAsync(string ac return await Senparc.Weixin.CommonAPIs.CommonJsonSend.SendAsync(null, url, data, CommonJsonSendType.POST, timeOut).ConfigureAwait(false); }, accessTokenOrAppKey).ConfigureAwait(false); - - } /// @@ -631,13 +592,10 @@ public static async Task UploadimgMediaAsync(string access var url = string.Format(Config.ApiWorkHost + "/cgi-bin/media/uploadimg?access_token={0}", accessToken.AsUrlData()); - var fileDictionary = new Dictionary(); fileDictionary["media"] = imgFile; return await Post.PostFileGetJsonAsync(CommonDI.CommonSP, url, null, fileDictionary, null, timeOut: timeOut).ConfigureAwait(false); }, accessTokenOrAppKey).ConfigureAwait(false); - - } #endregion } diff --git a/src/Senparc.Weixin.Work/Senparc.Weixin.Work/Senparc.Weixin.Work.net8.csproj b/src/Senparc.Weixin.Work/Senparc.Weixin.Work/Senparc.Weixin.Work.net8.csproj index 92d8e77a0d..efbec5df1d 100644 --- a/src/Senparc.Weixin.Work/Senparc.Weixin.Work/Senparc.Weixin.Work.net8.csproj +++ b/src/Senparc.Weixin.Work/Senparc.Weixin.Work/Senparc.Weixin.Work.net8.csproj @@ -1,7 +1,7 @@ net462;netstandard2.0;netstandard2.1 - 3.25.1 + 3.25.2 10.0 Senparc.Weixin.Work Senparc.Weixin.Work @@ -258,7 +258,7 @@ - + diff --git a/src/Senparc.Weixin.WxOpen.Middleware/Senparc.Weixin.WxOpen.Middleware.net8.csproj b/src/Senparc.Weixin.WxOpen.Middleware/Senparc.Weixin.WxOpen.Middleware.net8.csproj index d163c6eee5..5052a0086e 100644 --- a/src/Senparc.Weixin.WxOpen.Middleware/Senparc.Weixin.WxOpen.Middleware.net8.csproj +++ b/src/Senparc.Weixin.WxOpen.Middleware/Senparc.Weixin.WxOpen.Middleware.net8.csproj @@ -1,7 +1,7 @@ net462;netstandard2.0;netstandard2.1;netcoreapp3.1;net8.0 - 1.4.6 + 1.4.7 Senparc.Weixin.WxOpen.Middleware Senparc.Weixin.WxOpen.Middleware true @@ -69,7 +69,7 @@ - + diff --git a/src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen.Tests/Senparc.Weixin.WxOpen.Tests.net8.csproj b/src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen.Tests/Senparc.Weixin.WxOpen.Tests.net8.csproj index 92a0379f67..7e919c6376 100644 --- a/src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen.Tests/Senparc.Weixin.WxOpen.Tests.net8.csproj +++ b/src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen.Tests/Senparc.Weixin.WxOpen.Tests.net8.csproj @@ -35,7 +35,7 @@ - + diff --git a/src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen/Senparc.Weixin.WxOpen/Senparc.Weixin.WxOpen.net8.csproj b/src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen/Senparc.Weixin.WxOpen/Senparc.Weixin.WxOpen.net8.csproj index 216b724a27..c7cae05601 100644 --- a/src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen/Senparc.Weixin.WxOpen/Senparc.Weixin.WxOpen.net8.csproj +++ b/src/Senparc.Weixin.WxOpen/src/Senparc.Weixin.WxOpen/Senparc.Weixin.WxOpen/Senparc.Weixin.WxOpen.net8.csproj @@ -1,7 +1,7 @@ net462;netstandard2.0;netstandard2.1 - 3.23.1 + 3.23.2 9.0 Senparc.Weixin.WxOpen Senparc.Weixin.WxOpen @@ -225,7 +225,7 @@ - + diff --git a/src/Senparc.Weixin/Senparc.Weixin/Entities/SenparcWeixinSettings/SenparcWeixinSettingItem.Interfaces.cs b/src/Senparc.Weixin/Senparc.Weixin/Entities/SenparcWeixinSettings/SenparcWeixinSettingItem.Interfaces.cs index 023b7cf1a3..bf4346c727 100644 --- a/src/Senparc.Weixin/Senparc.Weixin/Entities/SenparcWeixinSettings/SenparcWeixinSettingItem.Interfaces.cs +++ b/src/Senparc.Weixin/Senparc.Weixin/Entities/SenparcWeixinSettings/SenparcWeixinSettingItem.Interfaces.cs @@ -223,6 +223,18 @@ public interface ISenparcWeixinSettingForTenpayV3 : ISenparcWeixinSettingBase /// string TenPayV3_APIv3Key { get; set; } + /// + /// 微信支付(V3)微信平台公钥(替换平台证书) + /// + string TenPayV3_TenPayPubKey { get; set; } + /// + /// 微信支付(V3)微信平台公钥ID(替换平台证书) + /// + string TenPayV3_TenPayPubKeyID { get; set; } + /// + /// 微信支付(V3)微信平台公钥 启动 + /// + bool TenPayV3_TenPayPubKeyEnable { get; set; } #endregion /// diff --git a/src/Senparc.Weixin/Senparc.Weixin/Entities/SenparcWeixinSettings/SenparcWeixinSettingItem.cs b/src/Senparc.Weixin/Senparc.Weixin/Entities/SenparcWeixinSettings/SenparcWeixinSettingItem.cs index 113b248984..7ab5420f6c 100644 --- a/src/Senparc.Weixin/Senparc.Weixin/Entities/SenparcWeixinSettings/SenparcWeixinSettingItem.cs +++ b/src/Senparc.Weixin/Senparc.Weixin/Entities/SenparcWeixinSettings/SenparcWeixinSettingItem.cs @@ -131,6 +131,9 @@ public SenparcWeixinSettingItem(ISenparcWeixinSettingForTenpayV3 setting, bool i TenPayV3_APIv3Key = setting.TenPayV3_APIv3Key; TenPayV3_PrivateKey = setting.TenPayV3_PrivateKey; TenPayV3_SerialNumber = setting.TenPayV3_SerialNumber; + + TenPayV3_TenPayPubKey = setting.TenPayV3_TenPayPubKey; + TenPayV3_WxOpenTenpayNotify = setting.TenPayV3_WxOpenTenpayNotify; } @@ -321,7 +324,42 @@ public virtual string TenPayV3_PrivateKey /// /// APIv3 密钥。在微信支付后台设置:https://pay.weixin.qq.com/index.php/core/cert/api_cert#/ /// - public string TenPayV3_APIv3Key { get; set; } + public virtual string TenPayV3_APIv3Key { get; set; } + + + private string _tenPayV3_WeixinPubKey; + /// + /// 微信支付(V3)公钥证书(替换平台证书) + /// + public virtual string TenPayV3_TenPayPubKey + { + get + { + return TenPayHelper.TryGetPublicKeyFromFile(ref _tenPayV3_WeixinPubKey); + } + set + { + _tenPayV3_WeixinPubKey = value; + } + } + + /// + /// 微信支付(V3)公钥证书序列号(替换平台证书) + /// + public virtual string TenPayV3_TenPayPubKeyID { get; set; } + + /// + /// 微信支付(V3)公钥证书 是否启用 + /// + public virtual bool TenPayV3_TenPayPubKeyEnable + { + get + { + return !string.IsNullOrWhiteSpace(TenPayV3_TenPayPubKey) && !string.IsNullOrWhiteSpace(TenPayV3_TenPayPubKeyID); + } + set { } + + } #endregion /// diff --git a/src/Senparc.Weixin/Senparc.Weixin/Helpers/TenPay/TenPayHelper.cs b/src/Senparc.Weixin/Senparc.Weixin/Helpers/TenPay/TenPayHelper.cs index 452794b0c9..6be9be0de0 100644 --- a/src/Senparc.Weixin/Senparc.Weixin/Helpers/TenPay/TenPayHelper.cs +++ b/src/Senparc.Weixin/Senparc.Weixin/Helpers/TenPay/TenPayHelper.cs @@ -84,5 +84,29 @@ public static string TryGetPrivateKeyFromFile(ref string tenPayV3_PrivateKey) } return tenPayV3_PrivateKey; } + + /// + /// 尝试从文件获取正确格式的私钥 + /// + /// + public static string TryGetPublicKeyFromFile(ref string tenPayV3_PubKey) + { + if (tenPayV3_PubKey != null && tenPayV3_PubKey.Length < 100 && tenPayV3_PubKey.StartsWith("~/")) + { + //虚拟路径 + //尝试读取文件 + var filePath = CO2NET.Utilities.ServerUtility.ContentRootMapPath(tenPayV3_PubKey); + if (!File.Exists(filePath)) + { + Senparc.Weixin.WeixinTrace.BaseExceptionLog(new WeixinException("TenPayV3_PubKey 证书文件不存在!" + filePath)); + } + + var fileContent = File.ReadAllText(filePath); + Regex regex = new Regex(@"(--([^\r\n])+--[\r\n]{0,1})|[\r\n]"); + var publicKey = regex.Replace(fileContent, ""); + tenPayV3_PubKey = publicKey; + } + return tenPayV3_PubKey; + } } } diff --git a/src/Senparc.Weixin/Senparc.Weixin/Senparc.Weixin.net8.csproj b/src/Senparc.Weixin/Senparc.Weixin/Senparc.Weixin.net8.csproj index 2121bb3a11..70353fdb32 100644 --- a/src/Senparc.Weixin/Senparc.Weixin/Senparc.Weixin.net8.csproj +++ b/src/Senparc.Weixin/Senparc.Weixin/Senparc.Weixin.net8.csproj @@ -2,7 +2,7 @@ net462;netstandard2.0;netstandard2.1 - 6.21.2 + 6.21.3 10.0 Senparc.Weixin Senparc.Weixin @@ -381,8 +381,8 @@ - - + + @@ -398,10 +398,6 @@ - - - - diff --git a/src/Senparc.Weixin/Senparc.WeixinTests/Senparc.WeixinTests.net8.csproj b/src/Senparc.Weixin/Senparc.WeixinTests/Senparc.WeixinTests.net8.csproj index d2f3adb172..c7ef3803fc 100644 --- a/src/Senparc.Weixin/Senparc.WeixinTests/Senparc.WeixinTests.net8.csproj +++ b/src/Senparc.Weixin/Senparc.WeixinTests/Senparc.WeixinTests.net8.csproj @@ -27,7 +27,7 @@ - +