mirror of
https://gitee.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat.git
synced 2025-07-17 10:41:58 +08:00
feat(work): 异步的消息加解密密钥管理器feat(tenpayv3): 异步的平台证书管理器
This commit is contained in:
parent
3fc3476771
commit
07d65882ff
@ -1,9 +1,9 @@
|
|||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients
|
||||||
{
|
{
|
||||||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
|
||||||
|
|
||||||
public interface IWechatTenpayCertificateManagerFactory
|
public interface IWechatTenpayCertificateManagerFactory
|
||||||
{
|
{
|
||||||
CertificateManager Create(string merchantId);
|
ICertificateManager Create(string merchantId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients.Implements
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients.Implements
|
||||||
{
|
{
|
||||||
@ -6,14 +6,14 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients.Imple
|
|||||||
|
|
||||||
internal partial class WechatTenpayCertificateManagerFactory : IWechatTenpayCertificateManagerFactory
|
internal partial class WechatTenpayCertificateManagerFactory : IWechatTenpayCertificateManagerFactory
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<string, CertificateManager> _dict;
|
private readonly ConcurrentDictionary<string, ICertificateManager> _dict;
|
||||||
|
|
||||||
public WechatTenpayCertificateManagerFactory()
|
public WechatTenpayCertificateManagerFactory()
|
||||||
{
|
{
|
||||||
_dict = new ConcurrentDictionary<string, CertificateManager>();
|
_dict = new ConcurrentDictionary<string, ICertificateManager>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CertificateManager Create(string merchantId)
|
public ICertificateManager Create(string merchantId)
|
||||||
{
|
{
|
||||||
// NOTICE:
|
// NOTICE:
|
||||||
// 这里的工厂方法是为了演示多租户而存在的,可根据商户号生成不同的证书管理器。
|
// 这里的工厂方法是为了演示多租户而存在的,可根据商户号生成不同的证书管理器。
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients
|
||||||
{
|
{
|
||||||
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
|
||||||
|
|
||||||
public interface IWechatTenpayCertificateManagerFactory
|
public interface IWechatTenpayCertificateManagerFactory
|
||||||
{
|
{
|
||||||
CertificateManager Create(string merchantId);
|
ICertificateManager Create(string merchantId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients.Implements
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Sample.Services.HttpClients.Implements
|
||||||
{
|
{
|
||||||
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
|
||||||
|
|
||||||
internal partial class WechatTenpayCertificateManagerFactory : IWechatTenpayCertificateManagerFactory
|
internal partial class WechatTenpayCertificateManagerFactory : IWechatTenpayCertificateManagerFactory
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<string, CertificateManager> _dict;
|
private readonly ConcurrentDictionary<string, ICertificateManager> _dict;
|
||||||
|
|
||||||
public WechatTenpayCertificateManagerFactory()
|
public WechatTenpayCertificateManagerFactory()
|
||||||
{
|
{
|
||||||
_dict = new ConcurrentDictionary<string, CertificateManager>();
|
_dict = new ConcurrentDictionary<string, ICertificateManager>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CertificateManager Create(string merchantId)
|
public CertificateManager Create(string merchantId)
|
||||||
|
@ -2,7 +2,7 @@ using System;
|
|||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.Ads.Settings
|
namespace SKIT.FlurlHttpClient.Wechat.Ads.Settings
|
||||||
{
|
{
|
||||||
public class Credentials
|
public sealed class Credentials
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 初始化客户端时 <see cref="WechatAdsClientOptions.AgencyId"/> 的副本。
|
/// 初始化客户端时 <see cref="WechatAdsClientOptions.AgencyId"/> 的副本。
|
||||||
|
@ -2,7 +2,7 @@ using System;
|
|||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.Api.Settings
|
namespace SKIT.FlurlHttpClient.Wechat.Api.Settings
|
||||||
{
|
{
|
||||||
public class Credentials
|
public sealed class Credentials
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 初始化客户端时 <see cref="WechatApiClientOptions.AppId"/> 的副本。
|
/// 初始化客户端时 <see cref="WechatApiClientOptions.AppId"/> 的副本。
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Settings
|
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Settings
|
||||||
{
|
{
|
||||||
public class Credentials
|
public sealed class Credentials
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 初始化客户端时 <see cref="WechatOpenAIClientOptions.AppId"/> 的副本。
|
/// 初始化客户端时 <see cref="WechatOpenAIClientOptions.AppId"/> 的副本。
|
||||||
|
@ -2,7 +2,7 @@ using System;
|
|||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Settings
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Settings
|
||||||
{
|
{
|
||||||
public class Credentials
|
public sealed class Credentials
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 初始化客户端时 <see cref="WechatTenpayBusinessClientOptions.PlatformId"/> 的副本。
|
/// 初始化客户端时 <see cref="WechatTenpayBusinessClientOptions.PlatformId"/> 的副本。
|
||||||
|
@ -2,7 +2,7 @@ using System;
|
|||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Settings
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Settings
|
||||||
{
|
{
|
||||||
public class Credentials
|
public sealed class Credentials
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 初始化客户端时 <see cref="WechatTenpayClientOptions.MerchantId"/> 的副本。
|
/// 初始化客户端时 <see cref="WechatTenpayClientOptions.MerchantId"/> 的副本。
|
||||||
|
@ -6,7 +6,7 @@ using System.Security.Cryptography.X509Certificates;
|
|||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Settings
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Settings
|
||||||
{
|
{
|
||||||
public class WechatpayHttpHandler : DelegatingHandler
|
public sealed class WechatpayHttpHandler : DelegatingHandler
|
||||||
{
|
{
|
||||||
public WechatpayHttpHandler(byte[]? certificateBytes, string? certificatePassword)
|
public WechatpayHttpHandler(byte[]? certificateBytes, string? certificatePassword)
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,7 @@ using System;
|
|||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||||
{
|
{
|
||||||
using SKIT.FlurlHttpClient.Primitives;
|
using SKIT.FlurlHttpClient.Primitives;
|
||||||
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants;
|
||||||
|
|
||||||
public static class WechatTenpayClientEventDecryptionExtensions
|
public static class WechatTenpayClientEventDecryptionExtensions
|
||||||
{
|
{
|
||||||
@ -52,7 +53,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
string plainJson;
|
string plainJson;
|
||||||
switch (webhookEventResource.Algorithm)
|
switch (webhookEventResource.Algorithm)
|
||||||
{
|
{
|
||||||
case Constants.EncryptionAlgorithms.AEAD_AES_256_GCM:
|
case EncryptionAlgorithms.AEAD_AES_256_GCM:
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -70,7 +71,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Constants.EncryptionAlgorithms.AEAD_SM4_128_GCM:
|
case EncryptionAlgorithms.AEAD_SM4_128_GCM:
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||||
{
|
{
|
||||||
using SKIT.FlurlHttpClient.Primitives;
|
using SKIT.FlurlHttpClient.Primitives;
|
||||||
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants;
|
||||||
|
|
||||||
public static class WechatTenpayClientEventVerificationExtensions
|
public static class WechatTenpayClientEventVerificationExtensions
|
||||||
{
|
{
|
||||||
@ -29,7 +32,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
webhookNonce: webhookNonce,
|
webhookNonce: webhookNonce,
|
||||||
webhookBody: webhookBody,
|
webhookBody: webhookBody,
|
||||||
webhookSignature: webhookSignature,
|
webhookSignature: webhookSignature,
|
||||||
webhookSignatureType: Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256,
|
webhookSignatureType: SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256,
|
||||||
webhookSerialNumber: webhookSerialNumber
|
webhookSerialNumber: webhookSerialNumber
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -64,5 +67,66 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
strSerialNumber: webhookSerialNumber
|
strSerialNumber: webhookSerialNumber
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>异步验证回调通知事件签名。</para>
|
||||||
|
/// <para>
|
||||||
|
/// REF: <br/>
|
||||||
|
/// <![CDATA[ https://pay.weixin.qq.com/docs/merchant/development/interface-rules/signature-verification.html ]]> <br/>
|
||||||
|
/// <![CDATA[ https://pay.weixin.qq.com/docs/partner/development/interface-rules/signature-verification.html ]]>
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
/// <param name="webhookTimestamp">微信回调通知中的 "Wechatpay-Timestamp" 请求标头。</param>
|
||||||
|
/// <param name="webhookNonce">微信回调通知中的 "Wechatpay-Nonce" 请求标头。</param>
|
||||||
|
/// <param name="webhookBody">微信回调通知中请求正文。</param>
|
||||||
|
/// <param name="webhookSignature">微信回调通知中的 "Wechatpay-Signature" 请求标头。</param>
|
||||||
|
/// <param name="webhookSerialNumber">微信回调通知中的 "Wechatpay-Serial" 请求标头。</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Task<ErroredResult> VerifyEventSignatureAsync(this WechatTenpayClient client, string webhookTimestamp, string webhookNonce, string webhookBody, string webhookSignature, string webhookSerialNumber, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return VerifyEventSignatureAsync(
|
||||||
|
client,
|
||||||
|
webhookTimestamp: webhookTimestamp,
|
||||||
|
webhookNonce: webhookNonce,
|
||||||
|
webhookBody: webhookBody,
|
||||||
|
webhookSignature: webhookSignature,
|
||||||
|
webhookSignatureType: SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256,
|
||||||
|
webhookSerialNumber: webhookSerialNumber,
|
||||||
|
cancellationToken: cancellationToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>异步验证回调通知事件签名。</para>
|
||||||
|
/// <para>
|
||||||
|
/// REF: <br/>
|
||||||
|
/// <![CDATA[ https://pay.weixin.qq.com/docs/merchant/development/interface-rules/signature-verification.html ]]> <br/>
|
||||||
|
/// <![CDATA[ https://pay.weixin.qq.com/docs/partner/development/interface-rules/signature-verification.html ]]>
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
/// <param name="webhookTimestamp">微信回调通知中的 "Wechatpay-Timestamp" 请求标头。</param>
|
||||||
|
/// <param name="webhookNonce">微信回调通知中的 "Wechatpay-Nonce" 请求标头。</param>
|
||||||
|
/// <param name="webhookBody">微信回调通知中请求正文。</param>
|
||||||
|
/// <param name="webhookSignature">微信回调通知中的 "Wechatpay-Signature" 请求标头。</param>
|
||||||
|
/// <param name="webhookSignatureType">微信回调通知中的 "Wechatpay-Signature-Type" 请求标头。</param>
|
||||||
|
/// <param name="webhookSerialNumber">微信回调通知中的 "Wechatpay-Serial" 请求标头。</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Task<ErroredResult> VerifyEventSignatureAsync(this WechatTenpayClient client, string webhookTimestamp, string webhookNonce, string webhookBody, string webhookSignature, string webhookSignatureType, string webhookSerialNumber, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||||
|
|
||||||
|
return WechatTenpayClientSigningExtensions.VerifySignatureAsync(
|
||||||
|
client,
|
||||||
|
strTimestamp: webhookTimestamp,
|
||||||
|
strNonce: webhookNonce,
|
||||||
|
strContent: webhookBody,
|
||||||
|
strSignature: webhookSignature,
|
||||||
|
strSignScheme: webhookSignatureType,
|
||||||
|
strSerialNumber: webhookSerialNumber,
|
||||||
|
cancellationToken: cancellationToken
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ using System.Collections.ObjectModel;
|
|||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||||
{
|
{
|
||||||
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants;
|
||||||
|
|
||||||
public static class WechatTenpayClientParameterExtensions
|
public static class WechatTenpayClientParameterExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -47,7 +49,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
{ "timeStamp", timestamp },
|
{ "timeStamp", timestamp },
|
||||||
{ "nonceStr", nonce },
|
{ "nonceStr", nonce },
|
||||||
{ "package", package },
|
{ "package", package },
|
||||||
{ "signType", Constants.SignTypes.RSA },
|
{ "signType", SignTypes.RSA },
|
||||||
{ "paySign", sign }
|
{ "paySign", sign }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,12 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||||
{
|
{
|
||||||
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants;
|
||||||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
|
||||||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities;
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities;
|
||||||
|
|
||||||
@ -30,8 +33,94 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
|
|
||||||
string signScheme = client.Credentials.SignScheme;
|
string signScheme = client.Credentials.SignScheme;
|
||||||
string algorithmType = // 签名方式与加密算法保持一致:RSA_SHA256 签名需 RSA 加密,SM3 签名需 SM2 加密
|
string algorithmType = // 签名方式与加密算法保持一致:RSA_SHA256 签名需 RSA 加密,SM3 签名需 SM2 加密
|
||||||
Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_RSA :
|
SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_RSA :
|
||||||
Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_SM2 :
|
SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_SM2 :
|
||||||
|
throw new WechatTenpayException($"Failed to encrypt request. Unsupported signing scheme: \"{signScheme}\".");
|
||||||
|
|
||||||
|
ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) =>
|
||||||
|
{
|
||||||
|
if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute)))
|
||||||
|
return (false, oldValue);
|
||||||
|
|
||||||
|
WechatTenpaySensitivePropertyAttribute? attribute = currentProp
|
||||||
|
.GetCustomAttributes<WechatTenpaySensitivePropertyAttribute>()
|
||||||
|
.FirstOrDefault(attr => attr.Scheme == signScheme);
|
||||||
|
if (attribute is null)
|
||||||
|
return (false, oldValue);
|
||||||
|
|
||||||
|
string certificate;
|
||||||
|
if (string.IsNullOrEmpty(request.WechatpayCertificateSerialNumber))
|
||||||
|
{
|
||||||
|
// 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的
|
||||||
|
IEnumerable<CertificateEntry> entries = client.PlatformCertificateManager.AllEntries()
|
||||||
|
.Where(e => e.AlgorithmType == algorithmType)
|
||||||
|
.OrderByDescending(e => e.ExpireTime);
|
||||||
|
if (!entries.Any())
|
||||||
|
{
|
||||||
|
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is empty. Please make sure you have downloaded platform (NOT merchant) certificates first.");
|
||||||
|
}
|
||||||
|
|
||||||
|
CertificateEntry entry = entries.First();
|
||||||
|
certificate = entry.Certificate;
|
||||||
|
request.WechatpayCertificateSerialNumber = entry.SerialNumber;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值
|
||||||
|
CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(request.WechatpayCertificateSerialNumber!);
|
||||||
|
if (!entry.HasValue)
|
||||||
|
{
|
||||||
|
throw new WechatTenpayException($"Failed to encrypt request, because the platform certificate manager does not contain a certificate matched the serial number \"{request.WechatpayCertificateSerialNumber}\". Please make sure you have downloaded platform (NOT merchant) certificates first.");
|
||||||
|
}
|
||||||
|
|
||||||
|
certificate = entry.Value.Certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
string newValue = GenerateEncryptedValueByCertificate(attribute.Algorithm, certificate, oldValue);
|
||||||
|
return (true, newValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
catch (WechatTenpayException)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new WechatTenpayException("Failed to encrypt request. Please see the inner exception for more details.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>异步加密请求中传入的敏感数据。该方法会改变传入的请求模型对象。</para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Task<TRequest> EncryptRequestSensitivePropertyAsync<TRequest>(this WechatTenpayClient client, TRequest request, CancellationToken cancellationToken = default)
|
||||||
|
where TRequest : WechatTenpayRequest
|
||||||
|
{
|
||||||
|
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||||
|
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||||
|
|
||||||
|
if (client.PlatformCertificateManager is not ICertificateManagerAsync)
|
||||||
|
{
|
||||||
|
// 降级为同步调用
|
||||||
|
return Task.FromResult(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool requireEncrypt = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute));
|
||||||
|
if (!requireEncrypt)
|
||||||
|
return Task.FromResult(request);
|
||||||
|
|
||||||
|
string signScheme = client.Credentials.SignScheme;
|
||||||
|
string algorithmType = // 签名方式与加密算法保持一致:RSA_SHA256 签名需 RSA 加密,SM3 签名需 SM2 加密
|
||||||
|
SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_RSA :
|
||||||
|
SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_SM2 :
|
||||||
throw new WechatTenpayException($"Failed to encrypt request. Unsupported signing scheme: \"{signScheme}\".");
|
throw new WechatTenpayException($"Failed to encrypt request. Unsupported signing scheme: \"{signScheme}\".");
|
||||||
|
|
||||||
ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) =>
|
ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) =>
|
||||||
@ -52,7 +141,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is not initialized.");
|
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is not initialized.");
|
||||||
|
|
||||||
// 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的
|
// 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的
|
||||||
IEnumerable<CertificateEntry> entries = client.PlatformCertificateManager.AllEntries()
|
IEnumerable<CertificateEntry> entries = ((ICertificateManagerAsync)client.PlatformCertificateManager)
|
||||||
|
.AllEntriesAsync(cancellationToken)
|
||||||
|
.ConfigureAwait(false)
|
||||||
|
.GetAwaiter()
|
||||||
|
.GetResult()
|
||||||
.Where(e => e.AlgorithmType == algorithmType)
|
.Where(e => e.AlgorithmType == algorithmType)
|
||||||
.OrderByDescending(e => e.ExpireTime);
|
.OrderByDescending(e => e.ExpireTime);
|
||||||
if (!entries.Any())
|
if (!entries.Any())
|
||||||
@ -67,7 +160,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值
|
// 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值
|
||||||
CertificateEntry? entry = client.PlatformCertificateManager?.GetEntry(request.WechatpayCertificateSerialNumber!);
|
CertificateEntry? entry = ((ICertificateManagerAsync)client.PlatformCertificateManager)
|
||||||
|
.GetEntryAsync(request.WechatpayCertificateSerialNumber!, cancellationToken)
|
||||||
|
.ConfigureAwait(false)
|
||||||
|
.GetAwaiter()
|
||||||
|
.GetResult();
|
||||||
if (!entry.HasValue)
|
if (!entry.HasValue)
|
||||||
{
|
{
|
||||||
throw new WechatTenpayException($"Failed to encrypt request, because the platform certificate manager does not contain a certificate matched the serial number \"{request.WechatpayCertificateSerialNumber}\". Please make sure you have downloaded platform (NOT merchant) certificates first.");
|
throw new WechatTenpayException($"Failed to encrypt request, because the platform certificate manager does not contain a certificate matched the serial number \"{request.WechatpayCertificateSerialNumber}\". Please make sure you have downloaded platform (NOT merchant) certificates first.");
|
||||||
@ -76,49 +173,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
certificate = entry.Value.Certificate;
|
certificate = entry.Value.Certificate;
|
||||||
}
|
}
|
||||||
|
|
||||||
string newValue;
|
string newValue = GenerateEncryptedValueByCertificate(attribute.Algorithm, certificate, oldValue);
|
||||||
switch (attribute.Algorithm)
|
|
||||||
{
|
|
||||||
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
|
|
||||||
{
|
|
||||||
newValue = RSAUtility.EncryptWithECBByCertificate(
|
|
||||||
certificatePem: certificate,
|
|
||||||
plainData: oldValue,
|
|
||||||
paddingMode: RSAUtility.PADDING_MODE_OAEPWITHSHA1ANDMGF1
|
|
||||||
)!;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
|
|
||||||
{
|
|
||||||
newValue = RSAUtility.EncryptWithECBByCertificate(
|
|
||||||
certificatePem: certificate,
|
|
||||||
plainData: oldValue,
|
|
||||||
paddingMode: RSAUtility.PADDING_MODE_PKCS1
|
|
||||||
)!;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Constants.EncryptionAlgorithms.SM2_C1C3C2_ASN1:
|
|
||||||
{
|
|
||||||
newValue = SM2Utility.EncryptByCertificate(
|
|
||||||
certificatePem: certificate,
|
|
||||||
plainData: oldValue,
|
|
||||||
asn1Encoding: true
|
|
||||||
)!;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
throw new WechatTenpayException($"Failed to encrypt request. Unsupported encryption algorithm: \"{attribute.Algorithm}\".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (true, newValue);
|
return (true, newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
return request;
|
return Task.FromResult(request);
|
||||||
}
|
}
|
||||||
catch (WechatTenpayException)
|
catch (WechatTenpayException)
|
||||||
{
|
{
|
||||||
@ -129,5 +188,35 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
throw new WechatTenpayException("Failed to encrypt request. Please see the inner exception for more details.", ex);
|
throw new WechatTenpayException("Failed to encrypt request. Please see the inner exception for more details.", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GenerateEncryptedValueByCertificate(string algorithm, string certificate, string value)
|
||||||
|
{
|
||||||
|
switch (algorithm)
|
||||||
|
{
|
||||||
|
case EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
|
||||||
|
return RSAUtility.EncryptWithECBByCertificate(
|
||||||
|
certificatePem: certificate,
|
||||||
|
plainData: value,
|
||||||
|
paddingMode: RSAUtility.PADDING_MODE_OAEPWITHSHA1ANDMGF1
|
||||||
|
)!;
|
||||||
|
|
||||||
|
case EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
|
||||||
|
return RSAUtility.EncryptWithECBByCertificate(
|
||||||
|
certificatePem: certificate,
|
||||||
|
plainData: value,
|
||||||
|
paddingMode: RSAUtility.PADDING_MODE_PKCS1
|
||||||
|
)!;
|
||||||
|
|
||||||
|
case EncryptionAlgorithms.SM2_C1C3C2_ASN1:
|
||||||
|
return SM2Utility.EncryptByCertificate(
|
||||||
|
certificatePem: certificate,
|
||||||
|
plainData: value,
|
||||||
|
asn1Encoding: true
|
||||||
|
)!;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new WechatTenpayException($"Failed to encrypt request. Unsupported encryption algorithm: \"{algorithm}\".");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ using System.Reflection;
|
|||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||||
{
|
{
|
||||||
using SKIT.FlurlHttpClient.Primitives;
|
using SKIT.FlurlHttpClient.Primitives;
|
||||||
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants;
|
||||||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Models;
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Models;
|
||||||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities;
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities;
|
||||||
|
|
||||||
@ -31,7 +32,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
|
|
||||||
switch (certificate.EncryptCertificate.Algorithm)
|
switch (certificate.EncryptCertificate.Algorithm)
|
||||||
{
|
{
|
||||||
case Constants.EncryptionAlgorithms.AEAD_AES_256_GCM:
|
case EncryptionAlgorithms.AEAD_AES_256_GCM:
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(client.Credentials.MerchantCertificatePrivateKey))
|
if (string.IsNullOrEmpty(client.Credentials.MerchantCertificatePrivateKey))
|
||||||
throw new WechatTenpayException("Failed to decrypt response, because the merchant private key is not set.");
|
throw new WechatTenpayException("Failed to decrypt response, because the merchant private key is not set.");
|
||||||
@ -45,7 +46,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Constants.EncryptionAlgorithms.AEAD_SM4_128_GCM:
|
case EncryptionAlgorithms.AEAD_SM4_128_GCM:
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(client.Credentials.MerchantV3Secret))
|
if (string.IsNullOrEmpty(client.Credentials.MerchantV3Secret))
|
||||||
throw new WechatTenpayException("Failed to decrypt response, because the merchant APIv3 secret is not set.");
|
throw new WechatTenpayException("Failed to decrypt response, because the merchant APIv3 secret is not set.");
|
||||||
@ -120,7 +121,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
string newValue;
|
string newValue;
|
||||||
switch (attribute.Algorithm)
|
switch (attribute.Algorithm)
|
||||||
{
|
{
|
||||||
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
|
case EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
|
||||||
{
|
{
|
||||||
newValue = RSAUtility.DecryptWithECB(
|
newValue = RSAUtility.DecryptWithECB(
|
||||||
privateKeyPem: client.Credentials.MerchantCertificatePrivateKey,
|
privateKeyPem: client.Credentials.MerchantCertificatePrivateKey,
|
||||||
@ -130,7 +131,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Constants.EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
|
case EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
|
||||||
{
|
{
|
||||||
newValue = RSAUtility.DecryptWithECB(
|
newValue = RSAUtility.DecryptWithECB(
|
||||||
privateKeyPem: client.Credentials.MerchantCertificatePrivateKey,
|
privateKeyPem: client.Credentials.MerchantCertificatePrivateKey,
|
||||||
@ -140,7 +141,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Constants.EncryptionAlgorithms.SM2_C1C3C2_ASN1:
|
case EncryptionAlgorithms.SM2_C1C3C2_ASN1:
|
||||||
{
|
{
|
||||||
newValue = SM2Utility.Decrypt(
|
newValue = SM2Utility.Decrypt(
|
||||||
privateKeyPem: client.Credentials.MerchantCertificatePrivateKey,
|
privateKeyPem: client.Credentials.MerchantCertificatePrivateKey,
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||||
{
|
{
|
||||||
using SKIT.FlurlHttpClient.Primitives;
|
using SKIT.FlurlHttpClient.Primitives;
|
||||||
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants;
|
||||||
|
|
||||||
public static class WechatTenpayClientResponseVerificationExtensions
|
public static class WechatTenpayClientResponseVerificationExtensions
|
||||||
{
|
{
|
||||||
@ -59,7 +62,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
responseNonce: responseNonce,
|
responseNonce: responseNonce,
|
||||||
responseBody: responseBody,
|
responseBody: responseBody,
|
||||||
responseSignature: responseSignature,
|
responseSignature: responseSignature,
|
||||||
responseSignatureType: Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256,
|
responseSignatureType: SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256,
|
||||||
responseSerialNumber
|
responseSerialNumber
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -94,5 +97,96 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
strSerialNumber: responseSerialNumber
|
strSerialNumber: responseSerialNumber
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>异步验证响应签名。</para>
|
||||||
|
/// <para>
|
||||||
|
/// REF: <br/>
|
||||||
|
/// <![CDATA[ https://pay.weixin.qq.com/docs/merchant/development/interface-rules/signature-verification.html ]]> <br/>
|
||||||
|
/// <![CDATA[ https://pay.weixin.qq.com/docs/partner/development/interface-rules/signature-verification.html ]]>
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TResponse"></typeparam>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
/// <param name="response"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Task<ErroredResult> VerifyResponseSignatureAsync<TResponse>(this WechatTenpayClient client, TResponse response, CancellationToken cancellationToken = default)
|
||||||
|
where TResponse : WechatTenpayResponse
|
||||||
|
{
|
||||||
|
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||||
|
if (response is null) throw new ArgumentNullException(nameof(response));
|
||||||
|
|
||||||
|
return VerifyResponseSignatureAsync(
|
||||||
|
client,
|
||||||
|
responseTimestamp: response.WechatpayTimestamp,
|
||||||
|
responseNonce: response.WechatpayNonce,
|
||||||
|
responseBody: Encoding.UTF8.GetString(response.GetRawBytes()),
|
||||||
|
responseSignature: response.WechatpaySignature,
|
||||||
|
responseSignatureType: response.WechatpaySignatureType,
|
||||||
|
responseSerialNumber: response.WechatpayCertificateSerialNumber,
|
||||||
|
cancellationToken: cancellationToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>异步验证响应签名。</para>
|
||||||
|
/// <para>
|
||||||
|
/// REF: <br/>
|
||||||
|
/// <![CDATA[ https://pay.weixin.qq.com/docs/merchant/development/interface-rules/signature-verification.html ]]> <br/>
|
||||||
|
/// <![CDATA[ https://pay.weixin.qq.com/docs/partner/development/interface-rules/signature-verification.html ]]>
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
/// <param name="responseTimestamp"></param>
|
||||||
|
/// <param name="responseNonce">。</param>
|
||||||
|
/// <param name="responseBody"></param>
|
||||||
|
/// <param name="responseSignature"></param>
|
||||||
|
/// <param name="responseSerialNumber"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Task<ErroredResult> VerifyResponseSignatureAsync(this WechatTenpayClient client, string responseTimestamp, string responseNonce, string responseBody, string responseSignature, string responseSerialNumber, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return VerifyResponseSignatureAsync(
|
||||||
|
client,
|
||||||
|
responseTimestamp: responseTimestamp,
|
||||||
|
responseNonce: responseNonce,
|
||||||
|
responseBody: responseBody,
|
||||||
|
responseSignature: responseSignature,
|
||||||
|
responseSignatureType: SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256,
|
||||||
|
responseSerialNumber: responseSerialNumber,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <para>异步验证响应签名。</para>
|
||||||
|
/// <para>
|
||||||
|
/// REF: <br/>
|
||||||
|
/// <![CDATA[ https://pay.weixin.qq.com/docs/merchant/development/interface-rules/signature-verification.html ]]> <br/>
|
||||||
|
/// <![CDATA[ https://pay.weixin.qq.com/docs/partner/development/interface-rules/signature-verification.html ]]>
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client"></param>
|
||||||
|
/// <param name="responseTimestamp"></param>
|
||||||
|
/// <param name="responseNonce">。</param>
|
||||||
|
/// <param name="responseBody"></param>
|
||||||
|
/// <param name="responseSignature"></param>
|
||||||
|
/// <param name="responseSignatureType"></param>
|
||||||
|
/// <param name="responseSerialNumber"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static Task<ErroredResult> VerifyResponseSignatureAsync(this WechatTenpayClient client, string responseTimestamp, string responseNonce, string responseBody, string responseSignature, string responseSignatureType, string responseSerialNumber, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||||
|
|
||||||
|
return WechatTenpayClientSigningExtensions.VerifySignatureAsync(
|
||||||
|
client,
|
||||||
|
strTimestamp: responseTimestamp,
|
||||||
|
strNonce: responseNonce,
|
||||||
|
strContent: responseBody,
|
||||||
|
strSignature: responseSignature,
|
||||||
|
strSignScheme: responseSignatureType,
|
||||||
|
strSerialNumber: responseSerialNumber,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||||
{
|
{
|
||||||
using SKIT.FlurlHttpClient.Primitives;
|
using SKIT.FlurlHttpClient.Primitives;
|
||||||
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants;
|
||||||
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings;
|
||||||
|
|
||||||
internal static class WechatTenpayClientSigningExtensions
|
internal static class WechatTenpayClientSigningExtensions
|
||||||
@ -11,36 +14,68 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
{
|
{
|
||||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||||
|
|
||||||
|
CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(strSerialNumber);
|
||||||
|
if (!entry.HasValue)
|
||||||
|
{
|
||||||
|
return ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate with serial number \"{strSerialNumber}\"."));
|
||||||
|
}
|
||||||
|
|
||||||
|
return GenerateSignatureResultByCertificate(
|
||||||
|
scheme: strSignScheme,
|
||||||
|
certificate: entry.Value.Certificate,
|
||||||
|
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
|
||||||
|
signature: strSignature
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<ErroredResult> VerifySignatureAsync(this WechatTenpayClient client, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignScheme, string strSerialNumber, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||||
|
|
||||||
|
if (client.PlatformCertificateManager is not ICertificateManagerAsync)
|
||||||
|
{
|
||||||
|
// 降级为同步调用
|
||||||
|
return VerifySignature(client, strTimestamp, strNonce, strContent, strSignature, strSignScheme, strSerialNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
CertificateEntry? entry = await ((ICertificateManagerAsync)client.PlatformCertificateManager).GetEntryAsync(strSerialNumber, cancellationToken).ConfigureAwait(false);
|
||||||
|
if (!entry.HasValue)
|
||||||
|
{
|
||||||
|
return ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate with serial number \"{strSerialNumber}\"."));
|
||||||
|
}
|
||||||
|
|
||||||
|
return GenerateSignatureResultByCertificate(
|
||||||
|
scheme: strSignScheme,
|
||||||
|
certificate: entry.Value.Certificate,
|
||||||
|
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
|
||||||
|
signature: strSignature
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GenerateMessageForSignature(string timestamp, string nonce, string body)
|
||||||
|
{
|
||||||
|
return $"{timestamp}\n{nonce}\n{body}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ErroredResult GenerateSignatureResultByCertificate(string scheme, string certificate, string message, string signature)
|
||||||
|
{
|
||||||
ErroredResult result;
|
ErroredResult result;
|
||||||
|
|
||||||
switch (strSignScheme)
|
switch (scheme)
|
||||||
{
|
{
|
||||||
case Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256:
|
case SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256:
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (client.PlatformCertificateManager is null)
|
|
||||||
{
|
|
||||||
result = ErroredResult.Fail(new Exception("The platform certificate manager is not initialized."));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(strSerialNumber);
|
|
||||||
if (!entry.HasValue)
|
|
||||||
{
|
|
||||||
result = ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate with serial number \"{strSerialNumber}\"."));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool valid = Utilities.RSAUtility.VerifyByCertificate(
|
bool valid = Utilities.RSAUtility.VerifyByCertificate(
|
||||||
certificatePem: entry.Value.Certificate,
|
certificatePem: certificate,
|
||||||
messageData: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
|
messageData: message,
|
||||||
encodingSignature: new EncodedString(strSignature, EncodingKinds.Base64)
|
encodingSignature: new EncodedString(signature, EncodingKinds.Base64)
|
||||||
);
|
);
|
||||||
if (valid)
|
if (valid)
|
||||||
result = ErroredResult.Ok();
|
result = ErroredResult.Ok();
|
||||||
else
|
else
|
||||||
result = ErroredResult.Fail(new Exception($"Signature does not match. Maybe \"{strSignature}\" is an illegal signature."));
|
result = ErroredResult.Fail(new Exception($"Signature does not match. Maybe \"{signature}\" is an illegal signature."));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -49,32 +84,19 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3:
|
case SignSchemes.WECHATPAY2_SM2_WITH_SM3:
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (client.PlatformCertificateManager is null)
|
|
||||||
{
|
|
||||||
result = ErroredResult.Fail(new Exception("The platform certificate manager is not initialized."));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(strSerialNumber);
|
|
||||||
if (!entry.HasValue)
|
|
||||||
{
|
|
||||||
result = ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate with serial number \"{strSerialNumber}\"."));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool valid = Utilities.SM2Utility.VerifyWithSM3ByCertificate(
|
bool valid = Utilities.SM2Utility.VerifyWithSM3ByCertificate(
|
||||||
certificatePem: entry.Value.Certificate,
|
certificatePem: certificate,
|
||||||
messageData: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
|
messageData: message,
|
||||||
encodingSignature: new EncodedString(strSignature, EncodingKinds.Base64)
|
encodingSignature: new EncodedString(signature, EncodingKinds.Base64)
|
||||||
);
|
);
|
||||||
if (valid)
|
if (valid)
|
||||||
result = ErroredResult.Ok();
|
result = ErroredResult.Ok();
|
||||||
else
|
else
|
||||||
result = ErroredResult.Fail(new Exception($"Signature does not match. Maybe \"{strSignature}\" is an illegal signature."));
|
result = ErroredResult.Fail(new Exception($"Signature does not match. Maybe \"{signature}\" is an illegal signature."));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -85,17 +107,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
result = ErroredResult.Fail(new Exception($"Unsupported signing scheme: \"{strSignScheme}\"."));
|
result = ErroredResult.Fail(new Exception($"Unsupported signing scheme: \"{scheme}\"."));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GenerateMessageForSignature(string timestamp, string nonce, string body)
|
|
||||||
{
|
|
||||||
return $"{timestamp}\n{nonce}\n{body}\n";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using Flurl.Http;
|
|||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Interceptors
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Interceptors
|
||||||
{
|
{
|
||||||
using SKIT.FlurlHttpClient.Internal;
|
using SKIT.FlurlHttpClient.Internal;
|
||||||
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants;
|
||||||
|
|
||||||
internal class WechatTenpayRequestSigningInterceptor : HttpInterceptor
|
internal class WechatTenpayRequestSigningInterceptor : HttpInterceptor
|
||||||
{
|
{
|
||||||
@ -38,7 +39,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Interceptors
|
|||||||
if (context.FlurlCall.HttpRequestMessage.Content is MultipartFormDataContent formdataContent)
|
if (context.FlurlCall.HttpRequestMessage.Content is MultipartFormDataContent formdataContent)
|
||||||
{
|
{
|
||||||
// NOTICE: multipart/form-data 文件上传请求的待签名参数需特殊处理
|
// NOTICE: multipart/form-data 文件上传请求的待签名参数需特殊处理
|
||||||
var httpContent = formdataContent.SingleOrDefault(e => Constants.FormDataFields.FORMDATA_META.Equals(e.Headers.ContentDisposition?.Name?.Trim('\"')));
|
var httpContent = formdataContent.SingleOrDefault(e => FormDataFields.FORMDATA_META.Equals(e.Headers.ContentDisposition?.Name?.Trim('\"')));
|
||||||
if (httpContent is not null)
|
if (httpContent is not null)
|
||||||
{
|
{
|
||||||
body = await _AsyncEx.RunTaskWithCancellationTokenAsync(httpContent.ReadAsStringAsync(), cancellationToken).ConfigureAwait(false);
|
body = await _AsyncEx.RunTaskWithCancellationTokenAsync(httpContent.ReadAsStringAsync(), cancellationToken).ConfigureAwait(false);
|
||||||
@ -54,7 +55,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Interceptors
|
|||||||
|
|
||||||
switch (_scheme)
|
switch (_scheme)
|
||||||
{
|
{
|
||||||
case Constants.SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256:
|
case SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256:
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -67,7 +68,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Interceptors
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Constants.SignSchemes.WECHATPAY2_SM2_WITH_SM3:
|
case SignSchemes.WECHATPAY2_SM2_WITH_SM3:
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -7,7 +7,14 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial struct CertificateEntry : IEquatable<CertificateEntry>
|
public partial struct CertificateEntry : IEquatable<CertificateEntry>
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 证书算法类型:RSA。
|
||||||
|
/// </summary>
|
||||||
public const string ALGORITHM_TYPE_RSA = "RSA";
|
public const string ALGORITHM_TYPE_RSA = "RSA";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 证书算法类型:SM2。
|
||||||
|
/// </summary>
|
||||||
public const string ALGORITHM_TYPE_SM2 = "SM2";
|
public const string ALGORITHM_TYPE_SM2 = "SM2";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -36,6 +43,14 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTimeOffset ExpireTime { get; }
|
public DateTimeOffset ExpireTime { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="algorithmType"></param>
|
||||||
|
/// <param name="serialNumber"></param>
|
||||||
|
/// <param name="certificate"></param>
|
||||||
|
/// <param name="effectiveTime"></param>
|
||||||
|
/// <param name="expireTime"></param>
|
||||||
[Newtonsoft.Json.JsonConstructor]
|
[Newtonsoft.Json.JsonConstructor]
|
||||||
[System.Text.Json.Serialization.JsonConstructor]
|
[System.Text.Json.Serialization.JsonConstructor]
|
||||||
public CertificateEntry(string algorithmType, string serialNumber, string certificate, DateTimeOffset effectiveTime, DateTimeOffset expireTime)
|
public CertificateEntry(string algorithmType, string serialNumber, string certificate, DateTimeOffset effectiveTime, DateTimeOffset expireTime)
|
||||||
@ -58,6 +73,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
|
|||||||
ExpireTime = expireTime;
|
ExpireTime = expireTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="algorithmType"></param>
|
||||||
|
/// <param name="certificate"></param>
|
||||||
public CertificateEntry(string algorithmType, string certificate)
|
public CertificateEntry(string algorithmType, string certificate)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(algorithmType))
|
if (string.IsNullOrEmpty(algorithmType))
|
||||||
@ -95,12 +115,21 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 返回一个布尔值,该值指示当前证书是否可用。
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
public bool IsAvailable()
|
public bool IsAvailable()
|
||||||
{
|
{
|
||||||
DateTimeOffset now = DateTimeOffset.Now;
|
DateTimeOffset now = DateTimeOffset.Now;
|
||||||
return EffectiveTime <= now && now < ExpireTime;
|
return EffectiveTime <= now && now < ExpireTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other"></param>
|
||||||
|
/// <returns></returns>
|
||||||
public bool Equals(CertificateEntry other)
|
public bool Equals(CertificateEntry other)
|
||||||
{
|
{
|
||||||
return string.Equals(AlgorithmType, other.AlgorithmType) &&
|
return string.Equals(AlgorithmType, other.AlgorithmType) &&
|
||||||
@ -108,6 +137,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
|
|||||||
string.Equals(SerialNumber, other.SerialNumber);
|
string.Equals(SerialNumber, other.SerialNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public override bool Equals(object? obj)
|
public override bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
if (ReferenceEquals(null, obj))
|
if (ReferenceEquals(null, obj))
|
||||||
@ -118,20 +148,23 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
|
|||||||
return Equals((CertificateEntry)obj);
|
return Equals((CertificateEntry)obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
{
|
{
|
||||||
#if NETFRAMEWORK || NETSTANDARD2_0
|
#if NETCOREAPP || NET5_0_OR_GREATER
|
||||||
return (AlgorithmType?.GetHashCode(), Certificate?.GetHashCode(), SerialNumber?.GetHashCode()).GetHashCode();
|
|
||||||
#else
|
|
||||||
return HashCode.Combine(AlgorithmType?.GetHashCode(), Certificate?.GetHashCode(), SerialNumber?.GetHashCode());
|
return HashCode.Combine(AlgorithmType?.GetHashCode(), Certificate?.GetHashCode(), SerialNumber?.GetHashCode());
|
||||||
|
#else
|
||||||
|
return (AlgorithmType?.GetHashCode(), Certificate?.GetHashCode(), SerialNumber?.GetHashCode()).GetHashCode();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public static bool operator ==(CertificateEntry left, CertificateEntry right)
|
public static bool operator ==(CertificateEntry left, CertificateEntry right)
|
||||||
{
|
{
|
||||||
return left.Equals(right);
|
return left.Equals(right);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public static bool operator !=(CertificateEntry left, CertificateEntry right)
|
public static bool operator !=(CertificateEntry left, CertificateEntry right)
|
||||||
{
|
{
|
||||||
return !left.Equals(right);
|
return !left.Equals(right);
|
||||||
@ -140,11 +173,23 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
|
|||||||
|
|
||||||
partial struct CertificateEntry
|
partial struct CertificateEntry
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 将指定的 <see cref="Models.QueryCertificatesResponse.Types.Certificate"/> 对象解析为 <see cref="CertificateEntry"/> 对象。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="algorithmType"></param>
|
||||||
|
/// <param name="certificate"></param>
|
||||||
|
/// <returns></returns>
|
||||||
public static CertificateEntry Parse(string algorithmType, Models.QueryCertificatesResponse.Types.Certificate certificate)
|
public static CertificateEntry Parse(string algorithmType, Models.QueryCertificatesResponse.Types.Certificate certificate)
|
||||||
{
|
{
|
||||||
return new CertificateEntry(algorithmType, certificate.SerialNumber, certificate.EncryptCertificate.CipherText, certificate.EffectiveTime, certificate.ExpireTime);
|
return new CertificateEntry(algorithmType, certificate.SerialNumber, certificate.EncryptCertificate.CipherText, certificate.EffectiveTime, certificate.ExpireTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将指定的 <see cref="Models.QueryCertificatesResponse.Types.Certificate"/> 对象解析为 <see cref="CertificateEntry"/> 对象。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="certificate"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="ArgumentException"></exception>
|
||||||
public static CertificateEntry Parse(Models.QueryCertificatesResponse.Types.Certificate certificate)
|
public static CertificateEntry Parse(Models.QueryCertificatesResponse.Types.Certificate certificate)
|
||||||
{
|
{
|
||||||
string? algorithmType = default!;
|
string? algorithmType = default!;
|
||||||
|
@ -2,45 +2,84 @@ using System;
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 微信商户平台证书管理器接口。
|
/// 微信商户平台证书管理器接口。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class CertificateManager
|
public interface ICertificateManager
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取存储的全部证书实体。
|
/// 获取存储的全部证书实体。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public abstract IEnumerable<CertificateEntry> AllEntries();
|
IEnumerable<CertificateEntry> AllEntries();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 添加一个证书实体。
|
/// 添加一个证书实体。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="entry"></param>
|
/// <param name="entry"></param>
|
||||||
public abstract void AddEntry(CertificateEntry entry);
|
void AddEntry(CertificateEntry entry);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 根据证书序列号获取证书实体。
|
/// 根据证书序列号获取证书实体。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="serialNumber"></param>
|
/// <param name="serialNumber"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public abstract CertificateEntry? GetEntry(string serialNumber);
|
CertificateEntry? GetEntry(string serialNumber);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 根据证书序列号移除证书实体。
|
/// 根据证书序列号移除证书实体。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="serialNumber"></param>
|
/// <param name="serialNumber"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public abstract bool RemoveEntry(string serialNumber);
|
bool RemoveEntry(string serialNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 微信商户平台证书管理器异步接口。
|
||||||
|
/// </summary>
|
||||||
|
public interface ICertificateManagerAsync : ICertificateManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 异步获取存储的全部证书实体。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<IEnumerable<CertificateEntry>> AllEntriesAsync(CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步添加一个证书实体。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entry"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task AddEntryAsync(CertificateEntry entry, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步根据证书序列号获取证书实体。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serialNumber"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<CertificateEntry?> GetEntryAsync(string serialNumber, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步根据证书序列号移除证书实体。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serialNumber"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<bool> RemoveEntryAsync(string serialNumber, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 一个基于内存实现的 <see cref="CertificateManager"/>。
|
/// 一个基于内存实现的 <see cref="CertificateManager"/>。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class InMemoryCertificateManager : CertificateManager
|
public sealed class InMemoryCertificateManager : ICertificateManager
|
||||||
{
|
{
|
||||||
private readonly ConcurrentDictionary<string, CertificateEntry> _dict;
|
private readonly ConcurrentDictionary<string, CertificateEntry> _dict;
|
||||||
|
|
||||||
@ -49,20 +88,22 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
|
|||||||
_dict = new ConcurrentDictionary<string, CertificateEntry>(StringComparer.OrdinalIgnoreCase);
|
_dict = new ConcurrentDictionary<string, CertificateEntry>(StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<CertificateEntry> AllEntries()
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<CertificateEntry> AllEntries()
|
||||||
{
|
{
|
||||||
return _dict.Values.Where(e => e.IsAvailable()).ToArray();
|
return _dict.Values.Where(static e => e.IsAvailable()).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void AddEntry(CertificateEntry entry)
|
/// <inheritdoc/>
|
||||||
|
public void AddEntry(CertificateEntry entry)
|
||||||
{
|
{
|
||||||
_dict.TryRemove(entry.SerialNumber, out _);
|
_dict.AddOrUpdate(entry.SerialNumber, (_) => entry, (_, _) => entry);
|
||||||
_dict.TryAdd(entry.SerialNumber, entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override CertificateEntry? GetEntry(string serialNumber)
|
/// <inheritdoc/>
|
||||||
|
public CertificateEntry? GetEntry(string serialNumber)
|
||||||
{
|
{
|
||||||
if (_dict.TryGetValue(serialNumber, out var entry))
|
if (_dict.TryGetValue(serialNumber, out CertificateEntry entry))
|
||||||
{
|
{
|
||||||
if (entry.IsAvailable())
|
if (entry.IsAvailable())
|
||||||
return entry;
|
return entry;
|
||||||
@ -73,7 +114,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool RemoveEntry(string serialNumber)
|
/// <inheritdoc/>
|
||||||
|
public bool RemoveEntry(string serialNumber)
|
||||||
{
|
{
|
||||||
return _dict.TryRemove(serialNumber, out _);
|
return _dict.TryRemove(serialNumber, out _);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ using System;
|
|||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
|
||||||
{
|
{
|
||||||
public class Credentials
|
public sealed class Credentials
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 初始化客户端时 <see cref="WechatTenpayClientOptions.MerchantId"/> 的副本。
|
/// 初始化客户端时 <see cref="WechatTenpayClientOptions.MerchantId"/> 的副本。
|
||||||
|
@ -6,6 +6,8 @@ using System.Web;
|
|||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
|
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
|
||||||
{
|
{
|
||||||
|
using SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants;
|
||||||
|
|
||||||
internal static class FileHttpContentBuilder
|
internal static class FileHttpContentBuilder
|
||||||
{
|
{
|
||||||
public static MultipartFormDataContent Build(string fileName, byte[] fileBytes, string fileContentType, string fileMetaJson, string formDataName = "file")
|
public static MultipartFormDataContent Build(string fileName, byte[] fileBytes, string fileContentType, string fileMetaJson, string formDataName = "file")
|
||||||
@ -35,7 +37,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
|
|||||||
string boundary = "--BOUNDARY--" + DateTimeOffset.Now.Ticks.ToString("x");
|
string boundary = "--BOUNDARY--" + DateTimeOffset.Now.Ticks.ToString("x");
|
||||||
MultipartFormDataContent httpContent = new MultipartFormDataContent(boundary);
|
MultipartFormDataContent httpContent = new MultipartFormDataContent(boundary);
|
||||||
httpContent.Headers.ContentType = MediaTypeHeaderValue.Parse($"multipart/form-data; boundary={boundary}");
|
httpContent.Headers.ContentType = MediaTypeHeaderValue.Parse($"multipart/form-data; boundary={boundary}");
|
||||||
httpContent.Add(metaContent, $"\"{Constants.FormDataFields.FORMDATA_META}\"");
|
httpContent.Add(metaContent, $"\"{FormDataFields.FORMDATA_META}\"");
|
||||||
httpContent.Add(fileContent, $"\"{formDataName}\"", $"\"{HttpUtility.UrlEncode(fileName)}\"");
|
httpContent.Add(fileContent, $"\"{formDataName}\"", $"\"{HttpUtility.UrlEncode(fileName)}\"");
|
||||||
return httpContent;
|
return httpContent;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取当前客户端使用的微信支付平台证书管理器。
|
/// 获取当前客户端使用的微信支付平台证书管理器。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Settings.CertificateManager PlatformCertificateManager { get; }
|
public Settings.ICertificateManager PlatformCertificateManager { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取是否自动加密请求中的敏感信息字段。
|
/// 获取是否自动加密请求中的敏感信息字段。
|
||||||
|
@ -82,6 +82,6 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
|||||||
/// 默认值:<see cref="Settings.InMemoryCertificateManager"/>
|
/// 默认值:<see cref="Settings.InMemoryCertificateManager"/>
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Settings.CertificateManager PlatformCertificateManager { get; set; } = new Settings.InMemoryCertificateManager();
|
public Settings.ICertificateManager PlatformCertificateManager { get; set; } = new Settings.InMemoryCertificateManager();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ using System;
|
|||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.Work.ExtendedSDK.Finance.Settings
|
namespace SKIT.FlurlHttpClient.Wechat.Work.ExtendedSDK.Finance.Settings
|
||||||
{
|
{
|
||||||
public class Credentials
|
public sealed class Credentials
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 初始化客户端时 <see cref="WechatWorkFinanceClientOptions.CorpId"/> 的副本。
|
/// 初始化客户端时 <see cref="WechatWorkFinanceClientOptions.CorpId"/> 的副本。
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace SKIT.FlurlHttpClient.Wechat.Work.Settings
|
namespace SKIT.FlurlHttpClient.Wechat.Work.Settings
|
||||||
{
|
{
|
||||||
public class Credentials
|
public sealed class Credentials
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 初始化客户端时 <see cref="WechatWorkClientOptions.CorpId"/> 的副本。
|
/// 初始化客户端时 <see cref="WechatWorkClientOptions.CorpId"/> 的副本。
|
||||||
|
Loading…
Reference in New Issue
Block a user