diff --git a/src/SKIT.FlurlHttpClient.Wechat.Ads/Exceptions/WechatAdsRequestAgencyTokenException.cs b/src/SKIT.FlurlHttpClient.Wechat.Ads/Exceptions/WechatAdsRequestAgencyTokenException.cs new file mode 100644 index 00000000..ea3c5c7f --- /dev/null +++ b/src/SKIT.FlurlHttpClient.Wechat.Ads/Exceptions/WechatAdsRequestAgencyTokenException.cs @@ -0,0 +1,24 @@ +using System; + +namespace SKIT.FlurlHttpClient.Wechat.Ads.Exceptions +{ + public class WechatAdsRequestAgencyTokenException : WechatAdsException + { + /// + internal WechatAdsRequestAgencyTokenException() + { + } + + /// + internal WechatAdsRequestAgencyTokenException(string message) + : base(message) + { + } + + /// + internal WechatAdsRequestAgencyTokenException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/src/SKIT.FlurlHttpClient.Wechat.Ads/Interceptors/WechatAdsAgencyTokenInterceptor.cs b/src/SKIT.FlurlHttpClient.Wechat.Ads/Interceptors/WechatAdsRequestAgencyTokenInterceptor.cs similarity index 76% rename from src/SKIT.FlurlHttpClient.Wechat.Ads/Interceptors/WechatAdsAgencyTokenInterceptor.cs rename to src/SKIT.FlurlHttpClient.Wechat.Ads/Interceptors/WechatAdsRequestAgencyTokenInterceptor.cs index 2a6fb24a..a9a6053a 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.Ads/Interceptors/WechatAdsAgencyTokenInterceptor.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.Ads/Interceptors/WechatAdsRequestAgencyTokenInterceptor.cs @@ -6,12 +6,12 @@ using Flurl.Http; namespace SKIT.FlurlHttpClient.Wechat.Ads.Interceptors { - internal class WechatAdsAgencyTokenInterceptor : FlurlHttpCallInterceptor + internal class WechatAdsRequestAgencyTokenInterceptor : FlurlHttpCallInterceptor { private readonly string _agencyId; private readonly string _agencyApiKey; - public WechatAdsAgencyTokenInterceptor(string agencyId, string agencyApiKey) + public WechatAdsRequestAgencyTokenInterceptor(string agencyId, string agencyApiKey) { _agencyId = agencyId; _agencyApiKey = agencyApiKey; @@ -20,6 +20,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Ads.Interceptors public override async Task BeforeCallAsync(FlurlCall flurlCall) { if (flurlCall == null) throw new ArgumentNullException(nameof(flurlCall)); + if (flurlCall.Completed) throw new Exceptions.WechatAdsRequestAgencyTokenException("This interceptor must be called before request completed."); string timestamp = DateTimeOffset.Now.ToLocalTime().ToUnixTimeSeconds().ToString(); string nonce = Guid.NewGuid().ToString("N"); diff --git a/src/SKIT.FlurlHttpClient.Wechat.Ads/WechatAdsClient.cs b/src/SKIT.FlurlHttpClient.Wechat.Ads/WechatAdsClient.cs index b084afcf..02fee8f0 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.Ads/WechatAdsClient.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.Ads/WechatAdsClient.cs @@ -30,7 +30,7 @@ namespace SKIT.FlurlHttpClient.Wechat.Ads FlurlClient.BaseUrl = options.Endpoints ?? WechatAdsEndpoints.DEFAULT; FlurlClient.WithTimeout(TimeSpan.FromMilliseconds(options.Timeout)); - Interceptors.Add(new Interceptors.WechatAdsAgencyTokenInterceptor( + Interceptors.Add(new Interceptors.WechatAdsRequestAgencyTokenInterceptor( agencyId: options.AgencyId, agencyApiKey: options.AgencyApiKey )); diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventVerificationExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventVerificationExtensions.cs index f7c10ee0..94985945 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventVerificationExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientEventVerificationExtensions.cs @@ -19,13 +19,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 /// 微信回调通知中的 Wechatpay-Signature 字段。 /// 微信回调通知中的 Wechatpay-Serial 字段。 /// - public static bool VerifyEventSignature( - this WechatTenpayClient client, - string callbackTimestamp, - string callbackNonce, - string callbackBody, - string callbackSignature, - string callbackSerialNumber) + public static bool VerifyEventSignature(this WechatTenpayClient client, string callbackTimestamp, string callbackNonce, string callbackBody, string callbackSignature, string callbackSerialNumber) { return VerifyEventSignature(client, callbackTimestamp, callbackNonce, callbackBody, callbackSignature, callbackSerialNumber, out _); } @@ -44,14 +38,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 /// /// /// - public static bool VerifyEventSignature( - this WechatTenpayClient client, - string callbackTimestamp, - string callbackNonce, - string callbackBody, - string callbackSignature, - string callbackSerialNumber, - out Exception? error) + public static bool VerifyEventSignature(this WechatTenpayClient client, string callbackTimestamp, string callbackNonce, string callbackBody, string callbackSignature, string callbackSerialNumber, out Exception? error) { if (client == null) throw new ArgumentNullException(nameof(client)); if (callbackTimestamp == null) throw new ArgumentNullException(nameof(callbackTimestamp)); diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseVerificationExtensions.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseVerificationExtensions.cs index 3d0e4fb1..fca44162 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseVerificationExtensions.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Extensions/WechatTenpayClientResponseVerificationExtensions.cs @@ -40,11 +40,55 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 if (client == null) throw new ArgumentNullException(nameof(client)); if (response == null) throw new ArgumentNullException(nameof(response)); + return VerifyResponseSignature(client, response.WechatpayTimestamp, response.WechatpayNonce, Encoding.UTF8.GetString(response.RawBytes), response.WechatpaySignature, response.WechatpayCertificateSerialNumber, out error); + } + + /// + /// 验证响应签名。 + /// REF: https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml + /// REF: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay4_1.shtml + /// + /// + /// + /// 。 + /// + /// + /// + /// + public static bool VerifyResponseSignature(this WechatTenpayClient client, string responseTimestamp, string responseNonce, string responseBody, string responseSignature, string responseSerialNumber) + { + return VerifyResponseSignature(client, responseTimestamp, responseNonce, responseBody, responseSignature, responseSerialNumber, out _); + } + + /// + /// 验证响应签名。 + /// REF: https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml + /// REF: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay4_1.shtml + /// + /// + /// + /// + /// 。 + /// + /// + /// + /// + /// + /// + public static bool VerifyResponseSignature(this WechatTenpayClient client, string responseTimestamp, string responseNonce, string responseBody, string responseSignature, string responseSerialNumber, out Exception? error) + { + if (client == null) throw new ArgumentNullException(nameof(client)); + if (responseTimestamp == null) throw new ArgumentNullException(nameof(responseTimestamp)); + if (responseNonce == null) throw new ArgumentNullException(nameof(responseNonce)); + if (responseBody == null) throw new ArgumentNullException(nameof(responseBody)); + if (responseSignature == null) throw new ArgumentNullException(nameof(responseSignature)); + if (responseSerialNumber == null) throw new ArgumentNullException(nameof(responseSerialNumber)); + if (client.PlatformCertificateManager != null) { try { - var cert = client.PlatformCertificateManager.GetEntry(response.WechatpayCertificateSerialNumber)!; + var cert = client.PlatformCertificateManager.GetEntry(responseSerialNumber)!; if (!cert.HasValue) { error = new Exceptions.WechatTenpayResponseVerificationException("Verify signature of response failed, because there is no platform certificate matched the serial number."); @@ -54,8 +98,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 error = null; return Utilities.RSAUtility.VerifyWithSHA256ByCertificate( certificate: cert.Value.Certificate, - plainText: GetPlainTextForSignature(response), - signature: response.WechatpaySignature + plainText: GetPlainTextForSignature(timestamp: responseTimestamp, nonce: responseNonce, body: responseBody), + signature: responseSignature ); } catch (Exception ex) @@ -69,11 +113,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 return false; } - private static string GetPlainTextForSignature(WechatTenpayResponse response) + private static string GetPlainTextForSignature(string timestamp, string nonce, string body) { - string timestamp = response.WechatpayTimestamp; - string nonce = response.WechatpayNonce; - string body = Encoding.UTF8.GetString(response.RawBytes); return $"{timestamp}\n{nonce}\n{body}\n"; } } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Interceptors/WechatTenpaySignInterceptor.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Interceptors/WechatTenpayRequestSignatureInterceptor.cs similarity index 83% rename from src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Interceptors/WechatTenpaySignInterceptor.cs rename to src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Interceptors/WechatTenpayRequestSignatureInterceptor.cs index c8e8aaf4..fc59e6db 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Interceptors/WechatTenpaySignInterceptor.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/Interceptors/WechatTenpayRequestSignatureInterceptor.cs @@ -6,14 +6,14 @@ using Flurl.Http; namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Interceptors { - internal class WechatTenpaySignInterceptor : FlurlHttpCallInterceptor + internal class WechatTenpayRequestSignatureInterceptor : FlurlHttpCallInterceptor { private readonly string _scheme; private readonly string _mchId; private readonly string _mchCertSn; private readonly string _mchCertPk; - public WechatTenpaySignInterceptor(string scheme, string mchId, string mchCertSn, string mchCertPk) + public WechatTenpayRequestSignatureInterceptor(string scheme, string mchId, string mchCertSn, string mchCertPk) { _scheme = scheme; _mchId = mchId; @@ -24,6 +24,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Interceptors public override async Task BeforeCallAsync(FlurlCall flurlCall) { if (flurlCall == null) throw new ArgumentNullException(nameof(flurlCall)); + if (flurlCall.Completed) throw new Exceptions.WechatTenpayRequestSignatureException("This interceptor must be called before request completed."); string method = flurlCall.HttpRequestMessage.Method.ToString().ToUpper(); string url = flurlCall.HttpRequestMessage.RequestUri?.PathAndQuery ?? string.Empty; @@ -68,8 +69,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Interceptors } string auth = $"mchid=\"{_mchId}\",nonce_str=\"{nonce}\",signature=\"{signText}\",timestamp=\"{timestamp}\",serial_no=\"{_mchCertSn}\""; - flurlCall.Request.Headers.Remove("Authorization"); - flurlCall.Request.WithHeader("Authorization", $"{_scheme} {auth}"); + flurlCall.Request.Headers.Remove(Contants.HttpHeaders.Authorization); + flurlCall.Request.WithHeader(Contants.HttpHeaders.Authorization, $"{_scheme} {auth}"); } } } diff --git a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClient.cs b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClient.cs index e48b3136..d7a3c35b 100644 --- a/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClient.cs +++ b/src/SKIT.FlurlHttpClient.Wechat.TenpayV3/WechatTenpayClient.cs @@ -55,7 +55,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3 FlurlClient.WithHeader(Contants.HttpHeaders.UserAgent, options.UserAgent); FlurlClient.WithTimeout(TimeSpan.FromMilliseconds(options.Timeout)); - Interceptors.Add(new Interceptors.WechatTenpaySignInterceptor( + Interceptors.Add(new Interceptors.WechatTenpayRequestSignatureInterceptor( scheme: options.SignAlgorithm, mchId: options.MerchantId, mchCertSn: options.MerchantCertificateSerialNumber,