mirror of
https://gitee.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat.git
synced 2025-07-15 05:13:17 +08:00
feat(tenpayv3): 提供 ICertificateStorer 接口,并基于此重新实现验签的扩展方法
This commit is contained in:
parent
165c25102a
commit
044fcf6d56
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants
|
||||
{
|
||||
public static class SignAlgorithms
|
||||
{
|
||||
/// <summary>
|
||||
/// WECHATPAY2-SHA256-RSA2048。
|
||||
/// </summary>
|
||||
public const string WECHATPAY2_SHA256_RSA2048 = "WECHATPAY2-SHA256-RSA2048";
|
||||
}
|
||||
}
|
@ -3,9 +3,9 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
{
|
||||
/// <summary>
|
||||
/// 为 <see cref="WechatTenpayClient"/> 提供回调通知事件的扩展方法。
|
||||
/// 为 <see cref="WechatTenpayClient"/> 提供回调通知事件敏感数据解密的扩展方法。
|
||||
/// </summary>
|
||||
public static class WechatTenpayClientEventExtensions
|
||||
public static class WechatTenpayClientEventDecryptionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>反序列化得到 <see cref="WechatTenpayEvent"/> 对象。</para>
|
@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
{
|
||||
/// <summary>
|
||||
/// 为 <see cref="WechatTenpayClient"/> 提供回调通知事件签名验证的扩展方法。
|
||||
/// </summary>
|
||||
public static class WechatTenpayClientEventVerificationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>验证回调通知事件签名。</para>
|
||||
/// <para>REF: https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml </para>
|
||||
/// <para>REF: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay4_1.shtml </para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse"></typeparam>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="callbackTimestamp">微信回调通知中的 Wechatpay-Timestamp 字段。</param>
|
||||
/// <param name="callbackNonce">微信回调通知中的 Wechatpay-Nonce 字段。</param>
|
||||
/// <param name="callbackBody">微信回调通知中请求正文。</param>
|
||||
/// <param name="callbackSignature">微信回调通知中的 Wechatpay-Signature 字段。</param>
|
||||
/// <param name="callbackSerialNumber">微信回调通知中的 Wechatpay-Serial 字段。</param>
|
||||
/// <returns></returns>
|
||||
public static bool VerifyEventSignature<TResponse>(
|
||||
this WechatTenpayClient client,
|
||||
string callbackTimestamp,
|
||||
string callbackNonce,
|
||||
string callbackBody,
|
||||
string callbackSignature,
|
||||
string callbackSerialNumber)
|
||||
where TResponse : WechatTenpayResponse
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (callbackTimestamp == null) throw new ArgumentNullException(nameof(callbackTimestamp));
|
||||
if (callbackNonce == null) throw new ArgumentNullException(nameof(callbackNonce));
|
||||
if (callbackBody == null) throw new ArgumentNullException(nameof(callbackBody));
|
||||
if (callbackSignature == null) throw new ArgumentNullException(nameof(callbackSignature));
|
||||
if (callbackSerialNumber == null) throw new ArgumentNullException(nameof(callbackSerialNumber));
|
||||
|
||||
if (client.WechatCertificateStorer == null)
|
||||
{
|
||||
throw new Exceptions.WechatTenpayResponseVerificationException($"You must set an instance of `{nameof(Settings.ICertificateStorer)}` at first.");
|
||||
}
|
||||
else
|
||||
{
|
||||
string? certificate = client.WechatCertificateStorer.Get(callbackSerialNumber);
|
||||
if (certificate == null)
|
||||
throw new Exceptions.WechatTenpayResponseVerificationException("Cannot get certificate by the serial number, may not be stored.");
|
||||
|
||||
string? publicKey = null;
|
||||
try
|
||||
{
|
||||
publicKey = Utilities.RSAUtility.ExportPublicKey(certificate);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exceptions.WechatTenpayResponseVerificationException("Cannot get public key of the certificate, may not be a valid certificate data.", ex);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Utilities.RSAUtility.VerifyWithSHA256(
|
||||
publicKey: publicKey,
|
||||
plainText: GetPlainTextForSignature(timestamp: callbackTimestamp, nonce: callbackNonce, body: callbackBody),
|
||||
signature: callbackSignature
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exceptions.WechatTenpayResponseVerificationException("Verify event signature failed.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPlainTextForSignature(string timestamp, string nonce, string body)
|
||||
{
|
||||
return $"{timestamp}\n{nonce}\n{body}\n";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
{
|
||||
/// <summary>
|
||||
/// 为 <see cref="WechatTenpayClient"/> 提供响应签名验证的扩展方法。
|
||||
/// </summary>
|
||||
public static class WechatTenpayClientResponseVerificationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>验证响应签名。</para>
|
||||
/// <para>REF: https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml </para>
|
||||
/// <para>REF: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay4_1.shtml </para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse"></typeparam>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="response"></param>
|
||||
/// <returns></returns>
|
||||
public static bool VerifyResponseSignature<TResponse>(this WechatTenpayClient client, TResponse response)
|
||||
where TResponse : WechatTenpayResponse
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (response == null) throw new ArgumentNullException(nameof(response));
|
||||
|
||||
if (client.WechatCertificateStorer == null)
|
||||
{
|
||||
throw new Exceptions.WechatTenpayResponseVerificationException($"You must set an instance of `{nameof(Settings.ICertificateStorer)}` at first.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (response.WechatpayCertSerialNumber == null)
|
||||
throw new Exceptions.WechatTenpayResponseVerificationException("Cannot read the serial number in headers of this response.");
|
||||
|
||||
string? certificate = client.WechatCertificateStorer.Get(response.WechatpayCertSerialNumber);
|
||||
if (certificate == null)
|
||||
throw new Exceptions.WechatTenpayResponseVerificationException("Cannot get certificate by the serial number, may not be stored.");
|
||||
|
||||
string? publicKey = null;
|
||||
try
|
||||
{
|
||||
publicKey = Utilities.RSAUtility.ExportPublicKey(certificate);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exceptions.WechatTenpayResponseVerificationException("Cannot get public key of the certificate, may not be a valid certificate data.", ex);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Utilities.RSAUtility.VerifyWithSHA256(
|
||||
publicKey: publicKey,
|
||||
plainText: GetPlainTextForSignature(response),
|
||||
signature: response.WechatpaySignature
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exceptions.WechatTenpayResponseVerificationException("Verify response signature failed.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPlainTextForSignature(WechatTenpayResponse response)
|
||||
{
|
||||
string timestamp = response.WechatpayTimestamp;
|
||||
string nonce = response.WechatpayNonce;
|
||||
string body = Encoding.UTF8.GetString(response.RawBytes);
|
||||
return $"{timestamp}\n{nonce}\n{body}\n";
|
||||
}
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
{
|
||||
/// <summary>
|
||||
/// 为 <see cref="WechatTenpayClient"/> 提供响应签名验证的扩展方法。
|
||||
/// </summary>
|
||||
public static class WechatTenpayClientResponseVerifyExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>验证响应签名(使用微信支付平台证书公钥)。</para>
|
||||
/// <para>REF: https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml </para>
|
||||
/// <para>REF: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay4_1.shtml </para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse"></typeparam>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="response"></param>
|
||||
/// <param name="publicKey"></param>
|
||||
/// <returns></returns>
|
||||
public static bool VerifyResponseSignature<TResponse>(this WechatTenpayClient client, TResponse response, string publicKey)
|
||||
where TResponse : WechatTenpayResponse
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (response == null) throw new ArgumentNullException(nameof(response));
|
||||
if (string.IsNullOrEmpty(publicKey)) throw new ArgumentNullException(publicKey);
|
||||
|
||||
try
|
||||
{
|
||||
return Utilities.RSAUtility.VerifyWithSHA256(
|
||||
publicKey: publicKey,
|
||||
plainText: GetPlainTextForSignature(response),
|
||||
signature: response.WechatpaySignature
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exceptions.WechatTenpayResponseVerificationException("Verify response signature failed.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>验证响应签名(使用微信支付平台证书)。</para>
|
||||
/// <para>REF: https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml </para>
|
||||
/// <para>REF: https://pay.weixin.qq.com/wiki/doc/apiv3_partner/wechatpay/wechatpay4_1.shtml </para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse"></typeparam>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="response"></param>
|
||||
/// <param name="certificate"></param>
|
||||
/// <returns></returns>
|
||||
public static bool VerifyResponseSignatureByCertificate<TResponse>(this WechatTenpayClient client, TResponse response, string certificate)
|
||||
where TResponse : WechatTenpayResponse
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (response == null) throw new ArgumentNullException(nameof(response));
|
||||
if (string.IsNullOrEmpty(certificate)) throw new ArgumentNullException(certificate);
|
||||
|
||||
try
|
||||
{
|
||||
return Utilities.RSAUtility.VerifyWithSHA256ByCertificate(
|
||||
certificate: certificate,
|
||||
plainText: GetPlainTextForSignature(response),
|
||||
signature: response.WechatpaySignature
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exceptions.WechatTenpayResponseVerificationException("Verify response signature failed.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPlainTextForSignature(WechatTenpayResponse response)
|
||||
{
|
||||
string timestamp = response.WechatpayTimestamp;
|
||||
string nonce = response.WechatpayNonce;
|
||||
string body = Encoding.UTF8.GetString(response.RawBytes);
|
||||
return $"{timestamp}\n{nonce}\n{body}\n";
|
||||
}
|
||||
}
|
||||
}
|
@ -53,7 +53,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Interceptors
|
||||
|
||||
switch (_scheme)
|
||||
{
|
||||
case WechatTenpayAuthSchemes.WECHATPAY2_SHA256_RSA2048:
|
||||
case Constants.SignAlgorithms.WECHATPAY2_SHA256_RSA2048:
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// 微信商户平台证书存储器接口。
|
||||
/// </summary>
|
||||
public interface ICertificateStorer
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据证书序列号获取证书(cer 格式)。
|
||||
/// </summary>
|
||||
/// <param name="serialNumber"></param>
|
||||
/// <returns></returns>
|
||||
string? Get(string serialNumber);
|
||||
|
||||
/// <summary>
|
||||
/// 设置证书序列号与证书(cer 格式)的映射关系。
|
||||
/// </summary>
|
||||
/// <param name="serialNumber"></param>
|
||||
/// <param name="certificate"></param>
|
||||
void Set(string serialNumber, string certificate);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// 一个基于内存实现的 <see cref="ICertificateStorer"/>。
|
||||
/// </summary>
|
||||
public class InMemoryCertificateStorer : ICertificateStorer
|
||||
{
|
||||
public IDictionary<string, string> _dict;
|
||||
|
||||
public InMemoryCertificateStorer()
|
||||
{
|
||||
_dict = new ConcurrentDictionary<string, string>();
|
||||
}
|
||||
|
||||
string? ICertificateStorer.Get(string serialNumber)
|
||||
{
|
||||
if (serialNumber == null) throw new ArgumentNullException(nameof(serialNumber));
|
||||
|
||||
return _dict[serialNumber];
|
||||
}
|
||||
|
||||
void ICertificateStorer.Set(string serialNumber, string certificate)
|
||||
{
|
||||
if (serialNumber == null) throw new ArgumentNullException(nameof(serialNumber));
|
||||
if (certificate == null) throw new ArgumentNullException(nameof(certificate));
|
||||
|
||||
_dict[serialNumber] = certificate;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
{
|
||||
/// <summary>
|
||||
/// 微信支付 API 接口签名认证方式。
|
||||
/// </summary>
|
||||
public static class WechatTenpayAuthSchemes
|
||||
{
|
||||
/// <summary>
|
||||
/// WECHATPAY2-SHA256-RSA2048(默认)。
|
||||
/// </summary>
|
||||
public const string WECHATPAY2_SHA256_RSA2048 = "WECHATPAY2-SHA256-RSA2048";
|
||||
}
|
||||
}
|
@ -39,6 +39,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
/// </summary>
|
||||
internal string WechatMerchantV3Secret { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前客户端使用的微信商户平台证书存储器。
|
||||
/// </summary>
|
||||
internal Settings.ICertificateStorer? WechatCertificateStorer { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前客户端使用的 JSON 序列化器。
|
||||
/// </summary>
|
||||
@ -59,6 +64,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
WechatMerchantCertSerialNumber = options.MerchantCertSerialNumber;
|
||||
WechatMerchantCertPrivateKey = options.MerchantCertPrivateKey;
|
||||
WechatMerchantV3Secret = options.MerchantV3Secret;
|
||||
WechatCertificateStorer = options.CertificateStorer;
|
||||
|
||||
FlurlClient.BaseUrl = options.Endpoints ?? WechatTenpayEndpoints.DEFAULT;
|
||||
FlurlClient.Headers.Remove("Accept");
|
||||
|
@ -34,9 +34,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置微信支付 API 签名认证方式。
|
||||
/// <para>默认值:<see cref="WechatTenpayAuthSchemes.WECHATPAY2_SHA256_RSA2048"/></para>
|
||||
/// <para>默认值:<see cref="Constants.SignAlgorithms.WECHATPAY2_SHA256_RSA2048"/></para>
|
||||
/// </summary>
|
||||
public string AuthScheme { get; set; } = WechatTenpayAuthSchemes.WECHATPAY2_SHA256_RSA2048;
|
||||
public string AuthScheme { get; set; } = Constants.SignAlgorithms.WECHATPAY2_SHA256_RSA2048;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置微信商户号。
|
||||
@ -57,5 +57,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
/// 获取或设置微信商户 API 证书私钥。
|
||||
/// </summary>
|
||||
public string MerchantCertPrivateKey { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置微信商户平台证书存储器。
|
||||
/// <para>默认值:<see cref="Settings.InMemoryCertificateStorer"/></para>
|
||||
/// </summary>
|
||||
public Settings.ICertificateStorer? CertificateStorer { get; set; } = new Settings.InMemoryCertificateStorer();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user