mirror of
https://gitee.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat.git
synced 2025-12-29 09:54:44 +08:00
feat(tenpayv3): 废弃原有的解密响应中加密字段的相关扩展方法,重新基于反射和特性实现
This commit is contained in:
3
src/SKIT.FlurlHttpClient.Wechat.TenpayV3/AssemblyInfo.cs
Normal file
3
src/SKIT.FlurlHttpClient.Wechat.TenpayV3/AssemblyInfo.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests")]
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants
|
||||
{
|
||||
internal static class FormDataFields
|
||||
{
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants
|
||||
{
|
||||
public static class SignAlgorithms
|
||||
{
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants
|
||||
{
|
||||
public static class SignTypes
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user