feat(tenpayv3): 废弃原有的解密响应中加密字段的相关扩展方法,重新基于反射和特性实现

This commit is contained in:
Fu Diwei
2021-11-25 18:05:22 +08:00
parent bfa6557314
commit 906c667117
25 changed files with 440 additions and 420 deletions

View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests")]

View File

@@ -0,0 +1,15 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class WechatTenpaySensitivePropertyAttribute : Attribute
{
public string Algorithm { get; }
public WechatTenpaySensitivePropertyAttribute(string algorithm)
{
Algorithm = algorithm;
}
}
}

View File

@@ -1,9 +1,9 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants
{
public static class EncryptionAlgorithms
{
public const string AEAD_AES_256_GCM = "AEAD_AES_256_GCM";
public const string RSA_2048_PKCS8_ECB = "RSA_2048_PKCS18_ECB";
}
}

View File

@@ -1,6 +1,4 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants
{
internal static class FormDataFields
{

View File

@@ -1,6 +1,4 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants
{
public static class SignAlgorithms
{

View File

@@ -1,6 +1,4 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants
{
public static class SignTypes
{

View File

@@ -70,7 +70,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
}
else
{
throw new Exceptions.WechatTenpayEventDecryptionException("Unknown encrypt algorithm of the resource.");
throw new Exceptions.WechatTenpayEventDecryptionException("Unsupported encrypt algorithm of the resource.");
}
return client.JsonSerializer.Deserialize<T>(plainJson);

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
{
@@ -15,7 +16,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
/// <param name="client"></param>
/// <param name="response"></param>
/// <returns></returns>
public static Models.GetEcommerceApplymentByOutRequestNumberResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.GetEcommerceApplymentByOutRequestNumberResponse response)
public static TResponse DecryptResponseSensitiveProperty<TResponse>(this WechatTenpayClient client, TResponse response)
where TResponse : WechatTenpayResponse
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (response == null) throw new ArgumentNullException(nameof(response));
@@ -26,159 +28,17 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
if (!response.IsSuccessful())
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful.");
if (response.AccountValidation != null)
try
{
IList<Exception> exceptions = new List<Exception>();
var accountValidationModel = response.AccountValidation;
if (!string.IsNullOrEmpty(accountValidationModel.AccountName))
// [GET] /certificates 接口的响应模型需特殊处理
if (response is Models.QueryCertificatesResponse queryCertificatesResponse)
{
try
if (queryCertificatesResponse.CertificateList == null)
return response;
foreach (var certificateModel in queryCertificatesResponse.CertificateList)
{
accountValidationModel.AccountName = Utilities.RSAUtility.DecryptWithECB(
client.Credentials.MerchantCertPrivateKey,
accountValidationModel.AccountName
);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
if (!string.IsNullOrEmpty(accountValidationModel.AccountNumber))
{
try
{
accountValidationModel.AccountNumber = Utilities.RSAUtility.DecryptWithECB(
client.Credentials.MerchantCertPrivateKey,
accountValidationModel.AccountNumber!
);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
if (exceptions.Any())
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", new AggregateException(exceptions));
}
return response;
}
/// <summary>
/// <para>解密响应中返回的敏感数据。该方法会改变传入的响应信息。</para>
/// </summary>
/// <param name="client"></param>
/// <param name="response"></param>
/// <returns></returns>
public static Models.GetEcommerceBillFundflowBillResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.GetEcommerceBillFundflowBillResponse response)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (response == null) throw new ArgumentNullException(nameof(response));
if (string.IsNullOrEmpty(client.Credentials.MerchantCertPrivateKey))
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because there is no merchant private key.");
if (!response.IsSuccessful())
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful.");
if (response.DownloadBillList != null)
{
IList<Exception> exceptions = new List<Exception>();
foreach (var downloadBillModel in response.DownloadBillList)
{
if (!string.IsNullOrEmpty(downloadBillModel.EncryptKey))
{
try
{
downloadBillModel.EncryptKey = Utilities.RSAUtility.DecryptWithECB(
client.Credentials.MerchantCertPrivateKey,
downloadBillModel.EncryptKey
);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
}
if (exceptions.Any())
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", new AggregateException(exceptions));
}
return response;
}
/// <summary>
/// <para>解密响应中返回的敏感数据。该方法会改变传入的响应信息。</para>
/// </summary>
/// <param name="client"></param>
/// <param name="response"></param>
/// <returns></returns>
public static Models.GetMerchantServiceComplaintByComplaintIdResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.GetMerchantServiceComplaintByComplaintIdResponse response)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (response == null) throw new ArgumentNullException(nameof(response));
if (string.IsNullOrEmpty(client.Credentials.MerchantCertPrivateKey))
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because there is no merchant private key.");
if (!response.IsSuccessful())
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful.");
if (response.ComplaintDetail != null)
{
if (!string.IsNullOrEmpty(response.PayerPhone))
{
try
{
response.PayerPhone = Utilities.RSAUtility.DecryptWithECB(
client.Credentials.MerchantCertPrivateKey,
response.PayerPhone!
);
}
catch (Exception ex)
{
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", ex);
}
}
}
return response;
}
/// <summary>
/// <para>解密响应中返回的敏感数据。该方法会改变传入的响应信息。</para>
/// </summary>
/// <param name="client"></param>
/// <param name="response"></param>
/// <returns></returns>
public static Models.QueryCertificatesResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.QueryCertificatesResponse response)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (response == null) throw new ArgumentNullException(nameof(response));
if (string.IsNullOrEmpty(client.Credentials.MerchantV3Secret))
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because there is no merchant secret.");
if (!response.IsSuccessful())
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful.");
if (response.CertificateList != null)
{
IList<Exception> exceptions = new List<Exception>();
foreach (var certificateModel in response.CertificateList)
{
if (Constants.EncryptionAlgorithms.AEAD_AES_256_GCM.Equals(certificateModel.EncryptCertificate?.Algorithm))
{
try
if (Constants.EncryptionAlgorithms.AEAD_AES_256_GCM.Equals(certificateModel.EncryptCertificate?.Algorithm))
{
certificateModel.EncryptCertificate.CipherText = Utilities.AESUtility.DecryptWithGCM(
key: client.Credentials.MerchantV3Secret,
@@ -187,266 +47,38 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
cipherText: certificateModel.EncryptCertificate.CipherText
);
}
catch (Exception ex)
else
{
exceptions.Add(ex);
throw new Exceptions.WechatTenpayResponseDecryptionException("Unsupported decryption algorithm.");
}
}
return response;
}
// 遍历并解密被标记为敏感数据的字段
Utilities.ReflectionUtility.ReplacePropertyStringValue(ref response, (obj, prop, value) =>
{
var attr = prop.GetCustomAttribute<WechatTenpaySensitivePropertyAttribute>();
if (attr == null)
return value;
if (Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB.Equals(attr.Algorithm))
{
return Utilities.RSAUtility.DecryptWithECB(
privateKey: client.Credentials.MerchantCertPrivateKey,
cipherText: value
);
}
else
{
exceptions.Add(new Exception("Unknown encrypt algorithm of the certificate."));
throw new Exceptions.WechatTenpayResponseDecryptionException("Unsupported decryption algorithm.");
}
}
if (exceptions.Any())
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", new AggregateException(exceptions));
});
}
return response;
}
/// <summary>
/// <para>解密响应中返回的敏感数据。该方法会改变传入的响应信息。</para>
/// </summary>
/// <param name="client"></param>
/// <param name="response"></param>
/// <returns></returns>
public static Models.QueryMerchantServiceComplaintsResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.QueryMerchantServiceComplaintsResponse response)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (response == null) throw new ArgumentNullException(nameof(response));
if (string.IsNullOrEmpty(client.Credentials.MerchantCertPrivateKey))
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because there is no merchant private key.");
if (!response.IsSuccessful())
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful.");
if (response.ComplaintList != null)
catch (Exception ex) when (!(ex is Exceptions.WechatTenpayResponseDecryptionException))
{
IList<Exception> exceptions = new List<Exception>();
foreach (var complaintModel in response.ComplaintList)
{
if (!string.IsNullOrEmpty(complaintModel.PayerPhone))
{
try
{
complaintModel.PayerPhone = Utilities.RSAUtility.DecryptWithECB(
client.Credentials.MerchantCertPrivateKey,
complaintModel.PayerPhone!
);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
}
if (exceptions.Any())
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", new AggregateException(exceptions));
}
return response;
}
/// <summary>
/// <para>解密响应中返回的敏感数据。该方法会改变传入的响应信息。</para>
/// </summary>
/// <param name="client"></param>
/// <param name="response"></param>
/// <returns></returns>
public static Models.QuerySmartGuidesResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.QuerySmartGuidesResponse response)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (response == null) throw new ArgumentNullException(nameof(response));
if (string.IsNullOrEmpty(client.Credentials.MerchantCertPrivateKey))
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because there is no merchant private key.");
if (!response.IsSuccessful())
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful.");
if (response.GuideList != null)
{
IList<Exception> exceptions = new List<Exception>();
foreach (var guideModel in response.GuideList)
{
if (!string.IsNullOrEmpty(guideModel.UserName))
{
try
{
guideModel.UserName = Utilities.RSAUtility.DecryptWithECB(
client.Credentials.MerchantCertPrivateKey,
guideModel.UserName
);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
if (!string.IsNullOrEmpty(guideModel.UserMobile))
{
try
{
guideModel.UserMobile = Utilities.RSAUtility.DecryptWithECB(
client.Credentials.MerchantCertPrivateKey,
guideModel.UserMobile
);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
}
if (exceptions.Any())
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", new AggregateException(exceptions));
}
return response;
}
/// <summary>
/// <para>解密响应中返回的敏感数据。该方法会改变传入的响应信息。</para>
/// </summary>
/// <param name="client"></param>
/// <param name="response"></param>
/// <returns></returns>
public static Models.GetTransferBatchDetailByOutDetailNumberResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.GetTransferBatchDetailByOutDetailNumberResponse response)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (response == null) throw new ArgumentNullException(nameof(response));
if (string.IsNullOrEmpty(client.Credentials.MerchantCertPrivateKey))
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because there is no merchant private key.");
if (!response.IsSuccessful())
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful.");
if (!string.IsNullOrEmpty(response.UserName))
{
try
{
response.UserName = Utilities.RSAUtility.DecryptWithECB(
client.Credentials.MerchantCertPrivateKey,
response.UserName
);
}
catch (Exception ex)
{
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", ex);
}
}
return response;
}
/// <summary>
/// <para>解密响应中返回的敏感数据。该方法会改变传入的响应信息。</para>
/// </summary>
/// <param name="client"></param>
/// <param name="response"></param>
/// <returns></returns>
public static Models.GetTransferBatchDetailByDetailIdResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.GetTransferBatchDetailByDetailIdResponse response)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (response == null) throw new ArgumentNullException(nameof(response));
if (string.IsNullOrEmpty(client.Credentials.MerchantCertPrivateKey))
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because there is no merchant private key.");
if (!response.IsSuccessful())
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful.");
if (!string.IsNullOrEmpty(response.UserName))
{
try
{
response.UserName = Utilities.RSAUtility.DecryptWithECB(
client.Credentials.MerchantCertPrivateKey,
response.UserName
);
}
catch (Exception ex)
{
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", ex);
}
}
return response;
}
/// <summary>
/// <para>解密响应中返回的敏感数据。该方法会改变传入的响应信息。</para>
/// </summary>
/// <param name="client"></param>
/// <param name="response"></param>
/// <returns></returns>
public static Models.GetPartnerTransferBatchDetailByOutDetailNumberResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.GetPartnerTransferBatchDetailByOutDetailNumberResponse response)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (response == null) throw new ArgumentNullException(nameof(response));
if (string.IsNullOrEmpty(client.Credentials.MerchantCertPrivateKey))
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because there is no merchant private key.");
if (!response.IsSuccessful())
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful.");
if (!string.IsNullOrEmpty(response.UserName))
{
try
{
response.UserName = Utilities.RSAUtility.DecryptWithECB(
client.Credentials.MerchantCertPrivateKey,
response.UserName
);
}
catch (Exception ex)
{
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", ex);
}
}
return response;
}
/// <summary>
/// <para>解密响应中返回的敏感数据。该方法会改变传入的响应信息。</para>
/// </summary>
/// <param name="client"></param>
/// <param name="response"></param>
/// <returns></returns>
public static Models.GetPartnerTransferBatchDetailByDetailIdResponse DecryptResponseEncryptedData(this WechatTenpayClient client, ref Models.GetPartnerTransferBatchDetailByDetailIdResponse response)
{
if (client == null) throw new ArgumentNullException(nameof(client));
if (response == null) throw new ArgumentNullException(nameof(response));
if (string.IsNullOrEmpty(client.Credentials.MerchantCertPrivateKey))
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because there is no merchant private key.");
if (!response.IsSuccessful())
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed, because the response is not successful.");
if (!string.IsNullOrEmpty(response.UserName))
{
try
{
response.UserName = Utilities.RSAUtility.DecryptWithECB(
client.Credentials.MerchantCertPrivateKey,
response.UserName
);
}
catch (Exception ex)
{
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed.", ex);
}
throw new Exceptions.WechatTenpayResponseDecryptionException("Decrypt response failed. Please see the `InnerException` for more details.", ex);
}
return response;

View File

@@ -45,6 +45,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("encrypt_key")]
[System.Text.Json.Serialization.JsonPropertyName("encrypt_key")]
[WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)]
public string EncryptKey { get; set; } = default!;
/// <summary>

View File

@@ -42,6 +42,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("ciphertext")]
[System.Text.Json.Serialization.JsonPropertyName("ciphertext")]
[WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.AEAD_AES_256_GCM)]
public string CipherText { get; set; } = default!;
}
}

View File

@@ -17,6 +17,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("account_name")]
[System.Text.Json.Serialization.JsonPropertyName("account_name")]
[WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)]
public string AccountName { get; set; } = default!;
/// <summary>
@@ -24,6 +25,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("account_no")]
[System.Text.Json.Serialization.JsonPropertyName("account_no")]
[WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)]
public string? AccountNumber { get; set; }
/// <summary>

View File

@@ -45,6 +45,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("encrypt_key")]
[System.Text.Json.Serialization.JsonPropertyName("encrypt_key")]
[WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)]
public string EncryptKey { get; set; } = default!;
/// <summary>

View File

@@ -94,6 +94,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("payer_phone")]
[System.Text.Json.Serialization.JsonPropertyName("payer_phone")]
[WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)]
public string? PayerPhone { get; set; }
/// <summary>

View File

@@ -58,6 +58,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("payer_phone")]
[System.Text.Json.Serialization.JsonPropertyName("payer_phone")]
[WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)]
public string? PayerPhone { get; set; }
/// <summary>

View File

@@ -20,6 +20,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("username")]
[System.Text.Json.Serialization.JsonPropertyName("username")]
[WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)]
public override string UserName { get; set; } = default!;
}
}

View File

@@ -45,6 +45,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("encrypt_key")]
[System.Text.Json.Serialization.JsonPropertyName("encrypt_key")]
[WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)]
public string EncryptKey { get; set; } = default!;
/// <summary>

View File

@@ -38,6 +38,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("name")]
[System.Text.Json.Serialization.JsonPropertyName("name")]
[WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)]
public string UserName { get; set; } = default!;
/// <summary>
@@ -45,6 +46,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("mobile")]
[System.Text.Json.Serialization.JsonPropertyName("mobile")]
[WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)]
public string UserMobile { get; set; } = default!;
/// <summary>

View File

@@ -83,6 +83,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("user_name")]
[System.Text.Json.Serialization.JsonPropertyName("user_name")]
[WechatTenpaySensitiveProperty(algorithm: Constants.EncryptionAlgorithms.RSA_2048_PKCS8_ECB)]
public virtual string UserName { get; set; } = default!;
/// <summary>

View File

@@ -1,5 +1,6 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
{
@@ -49,7 +50,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Settings
public override IEnumerable<CertificateEntry> AllEntries()
{
return _dict.Values;
return _dict.Values.Where(e => e.IsAvailable()).ToArray();
}
public override void AddEntry(CertificateEntry entry)

View File

@@ -0,0 +1,140 @@
using System;
using System.Collections;
using System.Reflection;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
{
internal static class ReflectionUtility
{
public delegate string ReplacePropertyStringValueReplacement(object obj, PropertyInfo prop, string value);
public static void ReplacePropertyStringValue<T>(ref T obj, ReplacePropertyStringValueReplacement replacement)
{
InnerReplacePropertyStringValue(ref obj, replacement, null);
}
private static void InnerReplacePropertyStringValue<T>(ref T obj, ReplacePropertyStringValueReplacement replacement, PropertyInfo? currentProp)
{
if (obj == null) throw new ArgumentNullException(nameof(obj));
if (replacement == null) throw new ArgumentNullException(nameof(replacement));
Type objType = obj.GetType();
if (!objType.IsClass)
throw new NotSupportedException();
if (objType.IsArray)
{
var array = (obj as Array)!;
for (int i = 0, len = array.Length; i < len; i++)
{
object? element = array.GetValue(i);
if (element is null)
continue;
Type elementType = element.GetType();
if (elementType == typeof(string))
{
if (currentProp == null)
continue;
string oldValue = (string)element!;
string newValue = replacement(obj, currentProp, oldValue);
array.SetValue(newValue, i);
}
else if (elementType.IsClass)
{
InnerReplacePropertyStringValue(ref element, replacement, currentProp);
array.SetValue(element, i);
}
else
{
continue;
}
}
}
else if (obj is IList list)
{
for (int i = 0, len = list.Count; i < len; i++)
{
object? element = list[i];
if (element is null)
continue;
Type elementType = element.GetType();
if (elementType == typeof(string))
{
if (currentProp == null)
continue;
string oldValue = (string)element!;
string newValue = replacement(obj, currentProp, oldValue);
list[i] = newValue;
}
else if (elementType.IsClass)
{
InnerReplacePropertyStringValue(ref element, replacement, currentProp);
list[i] = element;
}
else
{
continue;
}
}
}
else if (obj is IDictionary dict)
{
foreach (DictionaryEntry entry in dict)
{
object? entryValue = entry.Value;
if (entryValue is null)
continue;
Type entryValueType = entryValue.GetType();
if (entryValueType == typeof(string))
{
if (currentProp == null)
continue;
string oldValue = (string)entryValue!;
string newValue = replacement(obj, currentProp, oldValue);
dict[entry.Key] = newValue;
}
else if (entryValueType.IsClass)
{
InnerReplacePropertyStringValue(ref entryValue, replacement, currentProp);
dict[entry.Key] = entryValue;
}
else
{
continue;
}
}
}
else
{
foreach (var childProp in objType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
if (!childProp.CanWrite)
continue;
Type propType = childProp.PropertyType;
if (propType == typeof(string))
{
string oldValue = (string)childProp.GetValue(obj, null)!;
string newValue = replacement(obj, childProp, oldValue);
childProp.SetValue(obj, newValue);
}
else if (propType.IsClass)
{
object? value = childProp.GetValue(obj, null);
if (value is null)
continue;
InnerReplacePropertyStringValue(ref value, replacement, childProp);
childProp.SetValue(obj, value);
}
}
}
}
}
}

View File

@@ -23,7 +23,17 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
/// <summary>
/// 获取当前客户端使用的微信商户平台证书管理器。
/// </summary>
internal Settings.CertificateManager CertificateManager { get; }
public Settings.CertificateManager CertificateManager { get; }
/// <summary>
/// 获取是否自动加密请求中的敏感字段数据。
/// </summary>
protected bool AutoEncryptRequestSensitiveProperty { get; }
/// <summary>
/// 获取是否自动解密请求中的敏感字段数据。
/// </summary>
protected bool AutoDecryptResponseSensitiveProperty { get; }
/// <summary>
/// 用指定的配置项初始化 <see cref="WechatTenpayClient"/> 类的新实例。
@@ -35,6 +45,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
Credentials = new Settings.Credentials(options);
CertificateManager = options.CertificateManager;
AutoEncryptRequestSensitiveProperty = options.AutoEncryptRequestSensitiveProperty;
AutoDecryptResponseSensitiveProperty = options.AutoDecryptResponseSensitiveProperty;
FlurlClient.BaseUrl = options.Endpoints ?? WechatTenpayEndpoints.DEFAULT;
FlurlClient.Headers.Remove("Accept");
@@ -75,6 +87,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
flurlRequest.WithHeader("Wechatpay-Serial", request.WechatpayCertSerialNumber);
}
if (AutoDecryptResponseSensitiveProperty)
{
// this.EncryptRequestSensitiveProperty(request);
throw new NotImplementedException();
}
return flurlRequest;
}
@@ -152,6 +170,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
result.WechatpayTimestamp = flurlResponse.Headers.GetAll("Wechatpay-Timestamp").FirstOrDefault() ?? string.Empty;
result.WechatpaySignature = flurlResponse.Headers.GetAll("Wechatpay-Signature").FirstOrDefault() ?? string.Empty;
result.WechatpayCertSerialNumber = flurlResponse.Headers.GetAll("Wechatpay-Serial").FirstOrDefault() ?? string.Empty;
if (AutoDecryptResponseSensitiveProperty)
{
this.DecryptResponseSensitiveProperty(result);
}
return result;
}
}

View File

@@ -58,6 +58,16 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
/// </summary>
public string MerchantCertPrivateKey { get; set; } = default!;
/// <summary>
/// 获取或设置是否自动加密请求中的敏感字段数据。
/// </summary>
public bool AutoEncryptRequestSensitiveProperty { get; set; }
/// <summary>
/// 获取或设置是否自动解密请求中的敏感字段数据。
/// </summary>
public bool AutoDecryptResponseSensitiveProperty { get; set; }
/// <summary>
/// 获取或设置微信商户平台证书管理器。
/// <para>默认值:<see cref="Settings.InMemoryCertificateManager"/></para>