feat(tenpayv3): 适配微信支付平台公钥认证方案

This commit is contained in:
Fu Diwei 2024-11-05 21:19:50 +08:00
parent cb32782476
commit 4203fe6111
15 changed files with 753 additions and 169 deletions

View File

@ -122,14 +122,14 @@ manager.AddEntry(new CertificateEntry("证书算法,支持 RSA/SM2 两种类
当然,现在的平台证书离过期还有很久,你也可以选择“偷懒”:提前下载好平台证书,在程序启动时记录一次即可。
每个请求模型对象会包含一个名为 `WechatpayCertificateSerialNumber` 的公共字段,本库会根据该字段的值自动尝试在证书管理器中读取证书内容,并完成请求中敏感信息字段加密:
每个请求模型对象会包含一个名为 `WechatpaySerialNumber` 的公共字段,本库会根据该字段的值自动尝试在证书管理器中读取证书内容,并完成请求中敏感信息字段加密:
```csharp
request.WechatpayCertificateSerialNumber = "平台证书序列号";
request.WechatpaySerialNumber = "平台证书序列号";
client.EncryptRequestSensitiveProperty(request);
```
对于存在待加密敏感信息字段的请求模型对象而言,如果你不指定 `WechatpayCertificateSerialNumber` 字段的值,本库会自动从 `CertificateManager` 挑选一个离过期时间最远的证书。
对于存在待加密敏感信息字段的请求模型对象而言,如果你不指定 `WechatpaySerialNumber` 字段的值,本库会自动从 `CertificateManager` 挑选一个离过期时间最远的证书。
---

View File

@ -25,6 +25,48 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
if (client is null) throw new ArgumentNullException(nameof(client));
if (request is null) throw new ArgumentNullException(nameof(request));
switch (client.PlatformAuthScheme)
{
case PlatformAuthScheme.Certificate:
return EncryptRequestSensitivePropertyByCertificate<TRequest>(client, request);
case PlatformAuthScheme.PublicKey:
return EncryptRequestSensitivePropertyByPublicKey<TRequest>(client, request);
default:
throw new WechatTenpayException($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\".");
}
}
/// <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));
switch (client.PlatformAuthScheme)
{
case PlatformAuthScheme.Certificate:
return EncryptRequestSensitivePropertyByCertificateAsync<TRequest>(client, request);
case PlatformAuthScheme.PublicKey:
return EncryptRequestSensitivePropertyByPublicKeyAsync<TRequest>(client, request);
default:
throw new WechatTenpayException($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\".");
}
}
private static TRequest EncryptRequestSensitivePropertyByCertificate<TRequest>(this WechatTenpayClient client, TRequest request)
where TRequest : WechatTenpayRequest
{
try
{
bool requireEncrypt = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute));
@ -37,6 +79,34 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_SM2 :
throw new WechatTenpayException($"Failed to encrypt request. Unsupported signing scheme: \"{signScheme}\".");
string certificate;
if (string.IsNullOrEmpty(request.WechatpaySerialNumber))
{
// 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的
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.WechatpaySerialNumber = entry.SerialNumber;
}
else
{
// 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值
CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(request.WechatpaySerialNumber!);
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.WechatpaySerialNumber}\". Please make sure you have downloaded platform (NOT merchant) certificates first.");
}
certificate = entry.Value.Certificate;
}
ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) =>
{
if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute)))
@ -48,34 +118,6 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
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);
});
@ -92,30 +134,20 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
}
}
/// <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)
private static async Task<TRequest> EncryptRequestSensitivePropertyByCertificateAsync<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(EncryptRequestSensitiveProperty(client, request));
return EncryptRequestSensitivePropertyByCertificate(client, request);
}
try
{
bool requireEncrypt = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute));
if (!requireEncrypt)
return Task.FromResult(request);
return request;
string signScheme = client.Credentials.SignScheme;
string algorithmType = // 签名方式与加密算法保持一致RSA_SHA256 签名需 RSA 加密SM3 签名需 SM2 加密
@ -123,6 +155,36 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? CertificateEntry.ALGORITHM_TYPE_SM2 :
throw new WechatTenpayException($"Failed to encrypt request. Unsupported signing scheme: \"{signScheme}\".");
string certificate;
if (string.IsNullOrEmpty(request.WechatpaySerialNumber))
{
if (client.PlatformCertificateManager is null)
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is not initialized.");
// 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的
IEnumerable<CertificateEntry> entries = await ((ICertificateManagerAsync)client.PlatformCertificateManager).AllEntriesAsync(cancellationToken).ConfigureAwait(false);
entries = entries.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.WechatpaySerialNumber = entry.SerialNumber;
}
else
{
// 如果已在请求中指定特定的平台证书序列号,直接从管理器中取值
CertificateEntry? entry = await ((ICertificateManagerAsync)client.PlatformCertificateManager).GetEntryAsync(request.WechatpaySerialNumber!, cancellationToken).ConfigureAwait(false);
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.WechatpaySerialNumber}\". Please make sure you have downloaded platform (NOT merchant) certificates first.");
}
certificate = entry.Value.Certificate;
}
ReflectionHelper.ReplaceObjectStringProperties(request, (_, currentProp, oldValue) =>
{
if (currentProp is null || !currentProp.IsDefined(typeof(WechatTenpaySensitivePropertyAttribute)))
@ -134,50 +196,158 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
if (attribute is null)
return (false, oldValue);
string certificate;
if (string.IsNullOrEmpty(request.WechatpayCertificateSerialNumber))
{
if (client.PlatformCertificateManager is null)
throw new WechatTenpayException("Failed to encrypt request, because the platform certificate manager is not initialized.");
// 如果未在请求中指定特定的平台证书序列号,从管理器中取过期时间最远的
IEnumerable<CertificateEntry> entries = ((ICertificateManagerAsync)client.PlatformCertificateManager)
.AllEntriesAsync(cancellationToken)
.ConfigureAwait(false)
.GetAwaiter()
.GetResult()
.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 = ((ICertificateManagerAsync)client.PlatformCertificateManager)
.GetEntryAsync(request.WechatpayCertificateSerialNumber!, cancellationToken)
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
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 Task.FromResult(request);
return request;
}
catch (WechatTenpayException)
{
throw;
}
catch (Exception ex)
{
throw new WechatTenpayException("Failed to encrypt request. Please see the inner exception for more details.", ex);
}
}
private static TRequest EncryptRequestSensitivePropertyByPublicKey<TRequest>(this WechatTenpayClient client, TRequest request)
where TRequest : WechatTenpayRequest
{
try
{
bool requireEncrypt = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute));
if (!requireEncrypt)
return request;
string signScheme = client.Credentials.SignScheme;
string algorithmType = // 签名方式与加密算法保持一致RSA_SHA256 签名需 RSA 加密SM3 签名需 SM2 加密
SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(signScheme) ? PublicKeyEntry.ALGORITHM_TYPE_RSA :
SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? PublicKeyEntry.ALGORITHM_TYPE_SM2 :
throw new WechatTenpayException($"Failed to encrypt request. Unsupported signing scheme: \"{signScheme}\".");
string publicKey;
if (string.IsNullOrEmpty(request.WechatpaySerialNumber))
{
// 如果未在请求中指定特定的平台公钥序列号,从管理器中取第一个
IEnumerable<PublicKeyEntry> entries = client.PlatformPublicKeyManager.AllEntries()
.Where(e => e.AlgorithmType == algorithmType);
if (!entries.Any())
{
throw new WechatTenpayException("Failed to encrypt request, because the platform public key manager is empty.");
}
PublicKeyEntry entry = entries.First();
publicKey = entry.PublicKey;
request.WechatpaySerialNumber = entry.SerialNumber;
}
else
{
// 如果已在请求中指定特定的平台公钥序列号,直接从管理器中取值
PublicKeyEntry? entry = client.PlatformPublicKeyManager.GetEntry(request.WechatpaySerialNumber!);
if (!entry.HasValue)
{
throw new WechatTenpayException($"Failed to encrypt request, because the platform public key manager does not contain a key matched the serial number \"{request.WechatpaySerialNumber}\".");
}
publicKey = entry.Value.PublicKey;
}
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 newValue = GenerateEncryptedValueByPublicKey(attribute.Algorithm, publicKey, 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);
}
}
private static async Task<TRequest> EncryptRequestSensitivePropertyByPublicKeyAsync<TRequest>(this WechatTenpayClient client, TRequest request, CancellationToken cancellationToken = default)
where TRequest : WechatTenpayRequest
{
if (client.PlatformPublicKeyManager is not IPublicKeyManagerAsync)
{
// 降级为同步调用
return EncryptRequestSensitivePropertyByPublicKey(client, request);
}
try
{
bool requireEncrypt = request.GetType().IsDefined(typeof(WechatTenpaySensitiveAttribute));
if (!requireEncrypt)
return request;
string signScheme = client.Credentials.SignScheme;
string algorithmType = // 签名方式与加密算法保持一致RSA_SHA256 签名需 RSA 加密SM3 签名需 SM2 加密
SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256.Equals(signScheme) ? PublicKeyEntry.ALGORITHM_TYPE_RSA :
SignSchemes.WECHATPAY2_SM2_WITH_SM3.Equals(signScheme) ? PublicKeyEntry.ALGORITHM_TYPE_SM2 :
throw new WechatTenpayException($"Failed to encrypt request. Unsupported signing scheme: \"{signScheme}\".");
string publicKey;
if (string.IsNullOrEmpty(request.WechatpaySerialNumber))
{
if (client.PlatformPublicKeyManager is null)
throw new WechatTenpayException("Failed to encrypt request, because the platform public key manager is not initialized.");
// 如果未在请求中指定特定的平台公钥序列号,从管理器中第一个
IEnumerable<PublicKeyEntry> entries = await ((IPublicKeyManagerAsync)client.PlatformPublicKeyManager).AllEntriesAsync(cancellationToken).ConfigureAwait(false);
entries = entries.Where(e => e.AlgorithmType == algorithmType);
if (!entries.Any())
{
throw new WechatTenpayException("Failed to encrypt request, because the platform public key manager is empty.");
}
PublicKeyEntry entry = entries.First();
publicKey = entry.PublicKey;
request.WechatpaySerialNumber = entry.SerialNumber;
}
else
{
// 如果已在请求中指定特定的平台公钥序列号,直接从管理器中取值
PublicKeyEntry? entry = await ((IPublicKeyManagerAsync)client.PlatformPublicKeyManager).GetEntryAsync(request.WechatpaySerialNumber!, cancellationToken).ConfigureAwait(false);
if (!entry.HasValue)
{
throw new WechatTenpayException($"Failed to encrypt request, because the platform public key manager does not contain a key matched the serial number \"{request.WechatpaySerialNumber}\".");
}
publicKey = entry.Value.PublicKey;
}
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 newValue = GenerateEncryptedValueByPublicKey(attribute.Algorithm, publicKey, oldValue);
return (true, newValue);
});
return request;
}
catch (WechatTenpayException)
{
@ -194,22 +364,44 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
switch (algorithm)
{
case EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
return RSAUtility.EncryptWithECBByCertificate(
certificatePem: certificate,
case EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
{
string publicKey = RSAUtility.ExportPublicKeyFromCertificate(certificate);
return GenerateEncryptedValueByPublicKey(algorithm, publicKey, value);
}
case EncryptionAlgorithms.SM2_C1C3C2_ASN1:
{
string publicKey = SM2Utility.ExportPublicKeyFromCertificate(certificate);
return GenerateEncryptedValueByPublicKey(algorithm, publicKey, value);
}
default:
throw new WechatTenpayException($"Failed to encrypt request. Unsupported encryption algorithm: \"{algorithm}\".");
}
}
private static string GenerateEncryptedValueByPublicKey(string algorithm, string publicKey, string value)
{
switch (algorithm)
{
case EncryptionAlgorithms.RSA_2048_ECB_PKCS8_OAEP_WITH_SHA1_AND_MGF1:
return RSAUtility.EncryptWithECB(
publicKeyPem: publicKey,
plainData: value,
paddingMode: RSAUtility.PADDING_MODE_OAEPWITHSHA1ANDMGF1
)!;
case EncryptionAlgorithms.RSA_2048_ECB_PKCS1:
return RSAUtility.EncryptWithECBByCertificate(
certificatePem: certificate,
return RSAUtility.EncryptWithECB(
publicKeyPem: publicKey,
plainData: value,
paddingMode: RSAUtility.PADDING_MODE_PKCS1
)!;
case EncryptionAlgorithms.SM2_C1C3C2_ASN1:
return SM2Utility.EncryptByCertificate(
certificatePem: certificate,
return SM2Utility.Encrypt(
publicKeyPem: publicKey,
plainData: value,
asn1Encoding: true
)!;

View File

@ -35,7 +35,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
responseBody: Encoding.UTF8.GetString(response.GetRawBytes()),
responseSignature: response.WechatpaySignature,
responseSignatureType: response.WechatpaySignatureType,
responseSerialNumber: response.WechatpayCertificateSerialNumber
responseSerialNumber: response.WechatpaySerialNumber
);
}
@ -124,7 +124,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
responseBody: Encoding.UTF8.GetString(response.GetRawBytes()),
responseSignature: response.WechatpaySignature,
responseSignatureType: response.WechatpaySignatureType,
responseSerialNumber: response.WechatpayCertificateSerialNumber,
responseSerialNumber: response.WechatpaySerialNumber,
cancellationToken: cancellationToken
);
}

View File

@ -14,42 +14,98 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
if (client is null) throw new ArgumentNullException(nameof(client));
CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(strSerialNumber);
if (!entry.HasValue)
switch (client.PlatformAuthScheme)
{
return ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate matched the serial number \"{strSerialNumber}\"."));
}
case PlatformAuthScheme.Certificate:
{
CertificateEntry? entry = client.PlatformCertificateManager.GetEntry(strSerialNumber);
if (!entry.HasValue)
{
return ErroredResult.Fail(new Exception($"The platform certificate manager does not contain a certificate matched the serial number \"{strSerialNumber}\"."));
}
return GenerateSignatureResultByCertificate(
scheme: strSignScheme,
certificate: entry.Value.Certificate,
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
signature: strSignature
);
return GenerateSignatureResultByCertificate(
scheme: strSignScheme,
certificate: entry.Value.Certificate,
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
signature: strSignature
);
}
case PlatformAuthScheme.PublicKey:
{
PublicKeyEntry? entry = client.PlatformPublicKeyManager.GetEntry(strSerialNumber);
if (!entry.HasValue)
{
return ErroredResult.Fail(new Exception($"The platform public key manager does not contain a key matched the serial number \"{strSerialNumber}\"."));
}
return GenerateSignatureResultByPublicKey(
scheme: strSignScheme,
publicKey: entry.Value.PublicKey,
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
signature: strSignature
);
}
default:
return ErroredResult.Fail(new Exception($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\"."));
}
}
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)
switch (client.PlatformAuthScheme)
{
// 降级为同步调用
return VerifySignature(client, strTimestamp, strNonce, strContent, strSignature, strSignScheme, strSerialNumber);
}
case PlatformAuthScheme.Certificate:
{
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 matched the serial number \"{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 matched the serial number \"{strSerialNumber}\"."));
}
return GenerateSignatureResultByCertificate(
scheme: strSignScheme,
certificate: entry.Value.Certificate,
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
signature: strSignature
);
return GenerateSignatureResultByCertificate(
scheme: strSignScheme,
certificate: entry.Value.Certificate,
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
signature: strSignature
);
}
case PlatformAuthScheme.PublicKey:
{
if (client.PlatformCertificateManager is not IPublicKeyManagerAsync)
{
// 降级为同步调用
return VerifySignature(client, strTimestamp, strNonce, strContent, strSignature, strSignScheme, strSerialNumber);
}
PublicKeyEntry? entry = await ((IPublicKeyManagerAsync)client.PlatformPublicKeyManager).GetEntryAsync(strSerialNumber, cancellationToken).ConfigureAwait(false);
if (!entry.HasValue)
{
return ErroredResult.Fail(new Exception($"The platform public key manager does not contain a key matched the serial number \"{strSerialNumber}\"."));
}
return GenerateSignatureResultByPublicKey(
scheme: strSignScheme,
publicKey: entry.Value.PublicKey,
message: GenerateMessageForSignature(timestamp: strTimestamp, nonce: strNonce, body: strContent),
signature: strSignature
);
}
default:
return ErroredResult.Fail(new Exception($"Unsupported platform auth scheme: \"{client.PlatformAuthScheme}\"."));
}
}
private static string GenerateMessageForSignature(string timestamp, string nonce, string body)
@ -58,6 +114,42 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
}
private static ErroredResult GenerateSignatureResultByCertificate(string scheme, string certificate, string message, string signature)
{
string publicKey = string.Empty;
switch (scheme)
{
case SignSchemes.WECHATPAY2_RSA_2048_WITH_SHA256:
{
try
{
publicKey = Utilities.RSAUtility.ExportPublicKeyFromCertificate(certificate);
}
catch (Exception ex)
{
return ErroredResult.Fail(ex);
}
}
break;
case SignSchemes.WECHATPAY2_SM2_WITH_SM3:
{
try
{
publicKey = Utilities.SM2Utility.ExportPublicKeyFromCertificate(certificate);
}
catch (Exception ex)
{
return ErroredResult.Fail(ex);
}
}
break;
}
return GenerateSignatureResultByPublicKey(scheme, publicKey, message, signature);
}
private static ErroredResult GenerateSignatureResultByPublicKey(string scheme, string publicKey, string message, string signature)
{
ErroredResult result;
@ -67,8 +159,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
try
{
bool valid = Utilities.RSAUtility.VerifyWithSHA256ByCertificate(
certificatePem: certificate,
bool valid = Utilities.RSAUtility.VerifyWithSHA256(
publicKeyPem: publicKey,
messageData: message,
encodingSignature: new EncodedString(signature, EncodingKinds.Base64)
);
@ -88,8 +180,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
try
{
bool valid = Utilities.SM2Utility.VerifyWithSM3ByCertificate(
certificatePem: certificate,
bool valid = Utilities.SM2Utility.VerifyWithSM3(
publicKeyPem: publicKey,
messageData: message,
encodingSignature: new EncodedString(signature, EncodingKinds.Base64)
);

View File

@ -23,16 +23,16 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
/// </summary>
public string AlgorithmType { get; }
/// <summary>
/// 获取证书内容CRT/CER PEM 格式,即 -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----
/// </summary>
public string Certificate { get; }
/// <summary>
/// 获取证书序列号。
/// </summary>
public string SerialNumber { get; }
/// <summary>
/// 获取证书内容CRT/CER PEM 格式,即 -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----
/// </summary>
public string Certificate { get; }
/// <summary>
/// 获取生效时间。
/// </summary>

View File

@ -0,0 +1,18 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
{
/// <summary>
/// 微信支付平台认证方案。
/// </summary>
public enum PlatformAuthScheme
{
/// <summary>
/// 使用平台证书进行认证。
/// </summary>
Certificate = 0,
/// <summary>
/// 使用平台公钥进行认证。
/// </summary>
PublicKey = 1
}
}

View File

@ -0,0 +1,109 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
{
/// <summary>
/// 表示一个微信商户平台公钥实体。
/// </summary>
public partial struct PublicKeyEntry : IEquatable<PublicKeyEntry>
{
/// <summary>
/// 公钥算法类型RSA。
/// </summary>
public const string ALGORITHM_TYPE_RSA = CertificateEntry.ALGORITHM_TYPE_RSA;
/// <summary>
/// 公钥算法类型SM2。
/// </summary>
public const string ALGORITHM_TYPE_SM2 = CertificateEntry.ALGORITHM_TYPE_SM2;
/// <summary>
/// 获取公钥算法类型。
/// 取值范围RSA、SM2。
/// </summary>
public string AlgorithmType { get; }
/// <summary>
/// 获取公钥序列号。
/// </summary>
public string SerialNumber { get; }
/// <summary>
/// 获取公钥内容CRT/CER PEM 格式,即 -----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY-----
/// </summary>
public string PublicKey { get; }
/// <summary>
///
/// </summary>
/// <param name="algorithmType"></param>
/// <param name="serialNumber"></param>
/// <param name="publicKey"></param>
[Newtonsoft.Json.JsonConstructor]
[System.Text.Json.Serialization.JsonConstructor]
public PublicKeyEntry(string algorithmType, string serialNumber, string publicKey)
{
publicKey = publicKey?.Trim()!;
if (string.IsNullOrEmpty(algorithmType))
throw new ArgumentException("The value of `algorithmType` can not be empty.", nameof(algorithmType));
if (string.IsNullOrEmpty(publicKey))
throw new ArgumentException("The value of `publicKey` can not be empty.", nameof(publicKey));
if (string.IsNullOrEmpty(serialNumber))
throw new ArgumentException("The value of `serialNumber` can not be empty.", nameof(serialNumber));
if (!ALGORITHM_TYPE_RSA.Equals(algorithmType) && !ALGORITHM_TYPE_SM2.Equals(algorithmType))
throw new ArgumentException("The value of `algorithmType` an invalid value.", nameof(algorithmType));
if (!(publicKey.StartsWith("-----BEGIN PUBLIC KEY-----") && publicKey.EndsWith("-----END PUBLIC KEY-----")))
throw new ArgumentException("The value of `publicKey` is an invalid public key file content.", nameof(publicKey));
AlgorithmType = algorithmType;
SerialNumber = serialNumber.ToUpper();
PublicKey = publicKey;
}
/// <summary>
///
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
public bool Equals(PublicKeyEntry other)
{
return string.Equals(AlgorithmType, other.AlgorithmType) &&
string.Equals(PublicKey, other.PublicKey) &&
string.Equals(SerialNumber, other.SerialNumber);
}
/// <inheritdoc/>
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj))
return false;
if (GetType() != obj.GetType())
return false;
return Equals((PublicKeyEntry)obj);
}
/// <inheritdoc/>
public override int GetHashCode()
{
#if NETCOREAPP || NET5_0_OR_GREATER
return HashCode.Combine(AlgorithmType?.GetHashCode(), PublicKey?.GetHashCode(), SerialNumber?.GetHashCode());
#else
return (AlgorithmType?.GetHashCode(), PublicKey?.GetHashCode(), SerialNumber?.GetHashCode()).GetHashCode();
#endif
}
/// <inheritdoc/>
public static bool operator ==(PublicKeyEntry left, PublicKeyEntry right)
{
return left.Equals(right);
}
/// <inheritdoc/>
public static bool operator !=(PublicKeyEntry left, PublicKeyEntry right)
{
return !left.Equals(right);
}
}
}

View File

@ -0,0 +1,120 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
{
/// <summary>
/// 微信商户平台公钥管理器接口。
/// </summary>
public interface IPublicKeyManager
{
/// <summary>
/// 获取存储的全部公钥实体。
/// </summary>
/// <returns></returns>
IEnumerable<PublicKeyEntry> AllEntries();
/// <summary>
/// 添加一个公钥实体。
/// </summary>
/// <param name="entry"></param>
void AddEntry(PublicKeyEntry entry);
/// <summary>
/// 根据公钥序列号获取公钥实体。
/// </summary>
/// <param name="serialNumber"></param>
/// <returns></returns>
PublicKeyEntry? GetEntry(string serialNumber);
/// <summary>
/// 根据公钥序列号移除公钥实体。
/// </summary>
/// <param name="serialNumber"></param>
/// <returns></returns>
bool RemoveEntry(string serialNumber);
}
/// <summary>
/// 微信商户平台公钥管理器异步接口。
/// </summary>
public interface IPublicKeyManagerAsync : IPublicKeyManager
{
/// <summary>
/// 异步获取存储的全部公钥实体。
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<IEnumerable<PublicKeyEntry>> AllEntriesAsync(CancellationToken cancellationToken = default);
/// <summary>
/// 异步添加一个公钥实体。
/// </summary>
/// <param name="entry"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task AddEntryAsync(PublicKeyEntry entry, CancellationToken cancellationToken = default);
/// <summary>
/// 异步根据公钥序列号获取公钥实体。
/// </summary>
/// <param name="serialNumber"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task<PublicKeyEntry?> 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>
/// 一个基于内存实现的 <see cref="IPublicKeyManager"/>。
/// </summary>
public sealed class InMemoryPublicKeyManager : IPublicKeyManager
{
private readonly ConcurrentDictionary<string, PublicKeyEntry> _dict;
public InMemoryPublicKeyManager()
{
_dict = new ConcurrentDictionary<string, PublicKeyEntry>(StringComparer.OrdinalIgnoreCase);
}
/// <inheritdoc/>
public IEnumerable<PublicKeyEntry> AllEntries()
{
return _dict.Values.ToArray();
}
/// <inheritdoc/>
public void AddEntry(PublicKeyEntry entry)
{
_dict.AddOrUpdate(entry.SerialNumber, (_) => entry, (_, _) => entry);
}
/// <inheritdoc/>
public PublicKeyEntry? GetEntry(string serialNumber)
{
if (_dict.TryGetValue(serialNumber, out PublicKeyEntry entry))
{
return entry;
}
return null;
}
/// <inheritdoc/>
public bool RemoveEntry(string serialNumber)
{
return _dict.TryRemove(serialNumber, out _);
}
}
}

View File

@ -12,16 +12,28 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
/// </summary>
public class WechatTenpayClient : CommonClientBase, ICommonClient
{
private static readonly string ASSEMBLY_VERSION = Assembly.GetAssembly(typeof(WechatTenpayClient))!.GetName()!.Version!.ToString();
/// <summary>
/// 获取当前客户端使用的微信支付商户凭证。
/// </summary>
public Settings.Credentials Credentials { get; }
/// <summary>
/// 获取当前客户端使用的微信支付平台认证方案。
/// </summary>
public Settings.PlatformAuthScheme PlatformAuthScheme { get; }
/// <summary>
/// 获取当前客户端使用的微信支付平台证书管理器。
/// </summary>
public Settings.ICertificateManager PlatformCertificateManager { get; }
/// <summary>
/// 获取当前客户端使用的微信支付平台公钥管理器。
/// </summary>
public Settings.IPublicKeyManager PlatformPublicKeyManager { get; }
/// <summary>
/// 获取是否自动加密请求中的敏感信息字段。
/// </summary>
@ -53,14 +65,16 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
if (options is null) throw new ArgumentNullException(nameof(options));
Credentials = new Settings.Credentials(options);
PlatformAuthScheme = options.PlatformAuthScheme;
PlatformCertificateManager = options.PlatformCertificateManager;
PlatformPublicKeyManager = options.PlatformPublicKeyManager;
AutoEncryptRequestSensitiveProperty = options.AutoEncryptRequestSensitiveProperty;
AutoDecryptResponseSensitiveProperty = options.AutoDecryptResponseSensitiveProperty;
FlurlClient.BaseUrl = options.Endpoint ?? WechatTenpayEndpoints.DEFAULT;
FlurlClient.WithHeader(HttpHeaders.Accept, "application/json");
FlurlClient.WithHeader(HttpHeaders.AcceptLanguage, options.AcceptLanguage);
FlurlClient.WithHeader(HttpHeaders.UserAgent, $"OS/{Environment.OSVersion.Platform} SKIT.FlurlHttpClient.Wechat.Tenpay/{Assembly.GetAssembly(typeof(WechatTenpayClient))!.GetName().Version}");
FlurlClient.WithHeader(HttpHeaders.UserAgent, $"OS/{Environment.OSVersion.Platform} SKIT.FlurlHttpClient.Wechat.Tenpay/{ASSEMBLY_VERSION}");
FlurlClient.WithTimeout(options.Timeout <= 0 ? Timeout.InfiniteTimeSpan : TimeSpan.FromMilliseconds(options.Timeout));
Interceptors.Add(new Interceptors.WechatTenpayRequestSigningInterceptor(
@ -87,9 +101,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
this.EncryptRequestSensitiveProperty(request);
}
if (request.WechatpayCertificateSerialNumber is not null)
if (request.WechatpaySerialNumber is not null)
{
flurlRequest.WithHeader("Wechatpay-Serial", request.WechatpayCertificateSerialNumber);
flurlRequest.WithHeader("Wechatpay-Serial", request.WechatpaySerialNumber);
}
return flurlRequest;

View File

@ -73,12 +73,32 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
/// </summary>
public bool AutoDecryptResponseSensitiveProperty { get; set; }
/// <summary>
/// 获取或设置微信支付平台认证方案。
/// <para>默认值:<see cref="Settings.PlatformAuthScheme.Certificate"/></para>
/// </summary>
public Settings.PlatformAuthScheme PlatformAuthScheme { get; set; } = Settings.PlatformAuthScheme.Certificate;
/// <summary>
/// 获取或设置微信支付平台证书管理器。
/// <para>
/// 仅当 <see cref="PlatformAuthScheme"/> 的值为 <see cref="Settings.PlatformAuthScheme.Certificate"/> 时有效。
/// </para>
/// <para>
/// 默认值:<see cref="Settings.InMemoryCertificateManager"/>
/// </para>
/// </summary>
public Settings.ICertificateManager PlatformCertificateManager { get; set; } = new Settings.InMemoryCertificateManager();
/// <summary>
/// 获取或设置微信支付平台公钥管理器。
/// <para>
/// 仅当 <see cref="PlatformAuthScheme"/> 的值为 <see cref="Settings.PlatformAuthScheme.PublicKey"/> 时有效。
/// </para>
/// <para>
/// 默认值:<see cref="Settings.InMemoryPublicKeyManager"/>
/// </para>
/// </summary>
public Settings.IPublicKeyManager PlatformPublicKeyManager { get; set; } = new Settings.InMemoryPublicKeyManager();
}
}

View File

@ -1,3 +1,5 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
/// <summary>
@ -8,8 +10,16 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
/// <summary>
/// 获取或设置微信请求使用的微信支付平台证书序列号。
/// </summary>
[Obsolete("后续版本该属性将被移除,请使用 `WechatpaySerialNumber` 属性替代。", error: true)]
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
public virtual string? WechatpayCertificateSerialNumber { get; set; }
public virtual string? WechatpayCertificateSerialNumber { get { return WechatpaySerialNumber; } set { WechatpaySerialNumber = value; } }
/// <summary>
/// 获取或设置微信请求使用的微信支付平台证书或公钥序列号。
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
public virtual string? WechatpaySerialNumber { get; set; }
}
}

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
@ -66,9 +67,17 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
/// <summary>
/// 获取微信应答签名使用的微信支付平台证书序列号。
/// </summary>
[Obsolete("后续版本该属性将被移除,请使用 `WechatpaySerialNumber` 属性替代。", error: true)]
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
public string WechatpayCertificateSerialNumber { get { return GetRawHeaders().GetFirstValueOrEmpty("Wechatpay-Serial"); } }
public string WechatpayCertificateSerialNumber { get { return WechatpaySerialNumber; } }
/// <summary>
/// 获取微信应答签名使用的微信支付平台证书或公钥序列号。
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
public string WechatpaySerialNumber { get { return GetRawHeaders().GetFirstValueOrEmpty("Wechatpay-Serial"); } }
/// <summary>
/// 获取一个值,该值指示调用微信 API 是否成功。

View File

@ -38,7 +38,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotEqual(MOCK_PLAIN_STR, request.CertificateName!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.CertificateId!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.CertificateName!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -83,7 +83,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.Contact!.ContactName!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.Contact!.MobileNumber!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.Contact!.Email!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))

View File

@ -41,7 +41,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotEqual(MOCK_PLAIN_STR, request.AccountNumber!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.AccountName!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.AccountNumber!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -130,7 +130,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.UBOList![0].IdName!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.UBOList![0].IdNumber!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.UBOList![0].IdAddress!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -249,7 +249,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.Subject!.UBOList![0].IdAddress!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.BankAccount!.AccountName!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.BankAccount!.AccountNumber!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -308,7 +308,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
{
Assert.NotEqual(MOCK_PLAIN_STR, request.ReceiverList![0].Name!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.ReceiverList![0].Name!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -361,7 +361,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
{
Assert.NotEqual(MOCK_PLAIN_STR, request.AccountNumber!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.AccountNumber!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -474,7 +474,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.UBOList![0].IdAddress!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.BankAccount!.AccountName!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.BankAccount!.AccountNumber!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -527,7 +527,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
{
Assert.NotEqual(MOCK_PLAIN_STR, request.UserName!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.UserName!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -586,7 +586,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
{
Assert.NotEqual(MOCK_PLAIN_STR, request.ReceiverList![0].Name!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.ReceiverList![0].Name!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -639,7 +639,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
{
Assert.NotEqual(MOCK_PLAIN_STR, request.UserName!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.UserName!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -695,7 +695,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotEqual(MOCK_PLAIN_STR, request.RealName!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.BankAccountNumber!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.RealName!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -763,7 +763,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.Identity!.CredentialNumber!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.BankCard!.BankCardNumber!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.BankCard!.MobileNumber!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -816,7 +816,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
{
Assert.NotEqual(MOCK_PLAIN_STR, request.MobileNumber!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.MobileNumber!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -872,7 +872,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
{
Assert.NotEqual(MOCK_PLAIN_STR, request.MerchantContactInformation!.ConsultationPhoneNumber!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.MerchantContactInformation!.ConsultationPhoneNumber!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -944,7 +944,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.PayeeInfo!.BankAccount!.AccountNumber));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.PayeeInfo!.Identity!.IdName!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.PayeeInfo!.Identity!.IdNumber!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -1000,7 +1000,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotEqual(MOCK_PLAIN_STR, request.IdCardNumber!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.PhoneNumber!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.IdCardNumber!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -1056,7 +1056,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotEqual(MOCK_PLAIN_STR, request.IdCardNumber!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.UserName!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.IdCardNumber!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -1115,7 +1115,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotEqual(MOCK_PLAIN_STR, request.Buyer!.UserEmail!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.Buyer!.UserMobileNumber!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.Buyer!.UserEmail!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -1174,7 +1174,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotEqual(MOCK_PLAIN_STR, request.Buyer!.UserEmail!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.Buyer!.UserMobileNumber!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.Buyer!.UserEmail!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -1233,7 +1233,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotEqual(MOCK_PLAIN_STR, request.RealIdentity!.IdCardNumber);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.RealIdentity!.RealName));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.RealIdentity!.IdCardNumber));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -1292,7 +1292,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotEqual(MOCK_PLAIN_STR, request.RealIdentity!.IdCardNumber);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.RealIdentity!.RealName));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.RealIdentity!.IdCardNumber));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -1351,7 +1351,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotEqual(MOCK_PLAIN_STR, request.RealIdentity!.IdCardNumber);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.RealIdentity!.RealName));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.RealIdentity!.IdCardNumber));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -1407,7 +1407,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotEqual(MOCK_PLAIN_STR, request.IdCardNumber!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.UserName!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.IdCardNumber!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -1463,7 +1463,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotEqual(MOCK_PLAIN_STR, request.IdCardNumber!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.UserName!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.IdCardNumber!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -1519,7 +1519,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotEqual(MOCK_PLAIN_STR, request.IdCardNumber!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.UserName!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.IdCardNumber!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -1571,7 +1571,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
static void AssertMockRequestModel(Models.ApplyPlatformSolutionMerchantTransferReservationRequest request, Func<string, string> decryptor)
{
Assert.NotEqual(MOCK_PLAIN_STR, request.UserName!);
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -1630,7 +1630,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
{
Assert.NotEqual(MOCK_PLAIN_STR, request.ReceiverList![0].Name!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.ReceiverList![0].Name!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -1683,7 +1683,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
{
Assert.NotEqual(MOCK_PLAIN_STR, request.Name!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.Name!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -1739,7 +1739,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotEqual(MOCK_PLAIN_STR, request.RealName!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.BankAccountNumber!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.RealName!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -1795,7 +1795,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotEqual(MOCK_PLAIN_STR, request.UserMobile!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.UserName!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.UserMobile!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -1848,7 +1848,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
{
Assert.NotEqual(MOCK_PLAIN_STR, request.UserMobile!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.UserMobile!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -1904,7 +1904,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotEqual(MOCK_PLAIN_STR, request.UserMobile!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.UserName!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.UserMobile!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -1960,7 +1960,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotEqual(MOCK_PLAIN_STR, request.DriverIdCardNumber!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.DriverName!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.DriverIdCardNumber!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -2022,7 +2022,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotEqual(MOCK_PLAIN_STR, request.TransferDetailList![0].UserIdCardNumber!);
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.TransferDetailList![0].UserName!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.TransferDetailList![0].UserIdCardNumber!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))
@ -2084,7 +2084,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.Identify!.RealName!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.Identify!.CredentialNumber!));
Assert.Equal(MOCK_PLAIN_STR, decryptor.Invoke(request.BankCardNumber!));
Assert.Equal(MOCK_CERT_SN, request.WechatpayCertificateSerialNumber!, ignoreCase: true);
Assert.Equal(MOCK_CERT_SN, request.WechatpaySerialNumber!, ignoreCase: true);
}
if (!string.IsNullOrEmpty(TestConfigs.WechatMerchantRSACertificatePrivateKey))

View File

@ -22,7 +22,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotNull(response.WechatpayTimestamp);
Assert.NotNull(response.WechatpaySignature);
Assert.NotNull(response.WechatpaySignatureType);
Assert.NotNull(response.WechatpayCertificateSerialNumber);
Assert.NotNull(response.WechatpaySerialNumber);
}
[Fact(DisplayName = "测试用例:验证响应签名(基于 SM2")]
@ -41,7 +41,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests
Assert.NotNull(response.WechatpayTimestamp);
Assert.NotNull(response.WechatpaySignature);
Assert.NotNull(response.WechatpaySignatureType);
Assert.NotNull(response.WechatpayCertificateSerialNumber);
Assert.NotNull(response.WechatpaySerialNumber);
}
}
}