feat(tenpaybusiness): 升级公共组件

This commit is contained in:
Fu Diwei
2024-01-29 23:12:15 +08:00
committed by RHQYZ
parent fcc5e91510
commit 43eeb6fe6e
52 changed files with 448 additions and 628 deletions

View File

@@ -66,9 +66,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Events
/// 获取或设置支付成功时间。 /// 获取或设置支付成功时间。
/// </summary> /// </summary>
[Newtonsoft.Json.JsonProperty("pay_succ_time")] [Newtonsoft.Json.JsonProperty("pay_succ_time")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339NullableDateTimeOffsetConverter))] [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.Rfc3339DateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("pay_succ_time")] [System.Text.Json.Serialization.JsonPropertyName("pay_succ_time")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339NullableDateTimeOffsetConverter))] [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.Rfc3339DateTimeOffsetConverter))]
public DateTimeOffset? SucceedTime { get; set; } public DateTimeOffset? SucceedTime { get; set; }
} }
} }

View File

@@ -71,18 +71,18 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Events
/// 获取或设置退款成功时间。 /// 获取或设置退款成功时间。
/// </summary> /// </summary>
[Newtonsoft.Json.JsonProperty("succeeded_time")] [Newtonsoft.Json.JsonProperty("succeeded_time")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339NullableDateTimeOffsetConverter))] [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.Rfc3339DateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("succeeded_time")] [System.Text.Json.Serialization.JsonPropertyName("succeeded_time")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339NullableDateTimeOffsetConverter))] [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.Rfc3339DateTimeOffsetConverter))]
public DateTimeOffset? SucceedTime { get; set; } public DateTimeOffset? SucceedTime { get; set; }
/// <summary> /// <summary>
/// 获取或设置退款创建时间。 /// 获取或设置退款创建时间。
/// </summary> /// </summary>
[Newtonsoft.Json.JsonProperty("created_time")] [Newtonsoft.Json.JsonProperty("created_time")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339DateTimeOffsetConverter))] [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.Rfc3339DateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("created_time")] [System.Text.Json.Serialization.JsonPropertyName("created_time")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339DateTimeOffsetConverter))] [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.Rfc3339DateTimeOffsetConverter))]
public DateTimeOffset CreateTime { get; set; } public DateTimeOffset CreateTime { get; set; }
} }
} }

View File

@@ -1,24 +0,0 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Exceptions
{
public class WechatTenpayBusinessEventVerificationException : WechatTenpayBusinessException
{
/// <inheritdoc/>
internal WechatTenpayBusinessEventVerificationException()
{
}
/// <inheritdoc/>
internal WechatTenpayBusinessEventVerificationException(string message)
: base(message)
{
}
/// <inheritdoc/>
internal WechatTenpayBusinessEventVerificationException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@@ -1,24 +0,0 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Exceptions
{
public class WechatTenpayBusinessRequestEncryptionException : WechatTenpayBusinessException
{
/// <inheritdoc/>
internal WechatTenpayBusinessRequestEncryptionException()
{
}
/// <inheritdoc/>
internal WechatTenpayBusinessRequestEncryptionException(string message)
: base(message)
{
}
/// <inheritdoc/>
internal WechatTenpayBusinessRequestEncryptionException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@@ -1,24 +0,0 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Exceptions
{
public class WechatTenpayBusinessRequestSignatureException : WechatTenpayBusinessException
{
/// <inheritdoc/>
internal WechatTenpayBusinessRequestSignatureException()
{
}
/// <inheritdoc/>
internal WechatTenpayBusinessRequestSignatureException(string message)
: base(message)
{
}
/// <inheritdoc/>
internal WechatTenpayBusinessRequestSignatureException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@@ -1,24 +0,0 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Exceptions
{
public class WechatTenpayBusinessRequestTimeoutException : WechatTenpayBusinessException
{
/// <inheritdoc/>
internal WechatTenpayBusinessRequestTimeoutException()
{
}
/// <inheritdoc/>
internal WechatTenpayBusinessRequestTimeoutException(string message)
: base(message)
{
}
/// <inheritdoc/>
internal WechatTenpayBusinessRequestTimeoutException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@@ -1,24 +0,0 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Exceptions
{
public class WechatTenpayBusinessResponseDecryptionException : WechatTenpayBusinessException
{
/// <inheritdoc/>
internal WechatTenpayBusinessResponseDecryptionException()
{
}
/// <inheritdoc/>
internal WechatTenpayBusinessResponseDecryptionException(string message)
: base(message)
{
}
/// <inheritdoc/>
internal WechatTenpayBusinessResponseDecryptionException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@@ -1,24 +0,0 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Exceptions
{
public class WechatTenpayBusinessResponseVerificationException : WechatTenpayBusinessException
{
/// <inheritdoc/>
internal WechatTenpayBusinessResponseVerificationException()
{
}
/// <inheritdoc/>
internal WechatTenpayBusinessResponseVerificationException(string message)
: base(message)
{
}
/// <inheritdoc/>
internal WechatTenpayBusinessResponseVerificationException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@@ -1,4 +1,4 @@
using System; using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
{ {
@@ -8,11 +8,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
/// <para>反序列化得到 <see cref="WechatTenpayBusinessEvent"/> 对象。</para> /// <para>反序列化得到 <see cref="WechatTenpayBusinessEvent"/> 对象。</para>
/// </summary> /// </summary>
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="callbackJson"></param> /// <param name="webhookJson"></param>
/// <returns></returns> /// <returns></returns>
public static WechatTenpayBusinessEvent DeserializeEvent(this WechatTenpayBusinessClient client, string callbackJson) public static WechatTenpayBusinessEvent DeserializeEvent(this WechatTenpayBusinessClient client, string webhookJson)
{ {
return DeserializeEvent<WechatTenpayBusinessEvent>(client, callbackJson); return DeserializeEvent<WechatTenpayBusinessEvent>(client, webhookJson);
} }
/// <summary> /// <summary>
@@ -20,15 +20,15 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
/// </summary> /// </summary>
/// <typeparam name="TEvent"></typeparam> /// <typeparam name="TEvent"></typeparam>
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="callbackJson"></param> /// <param name="webhookJson"></param>
/// <returns></returns> /// <returns></returns>
public static TEvent DeserializeEvent<TEvent>(this WechatTenpayBusinessClient client, string callbackJson) public static TEvent DeserializeEvent<TEvent>(this WechatTenpayBusinessClient client, string webhookJson)
where TEvent : WechatTenpayBusinessEvent where TEvent : WechatTenpayBusinessEvent
{ {
if (client == null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
if (string.IsNullOrEmpty(callbackJson)) throw new ArgumentNullException(callbackJson); if (webhookJson is null) throw new ArgumentNullException(webhookJson);
return client.JsonSerializer.Deserialize<TEvent>(callbackJson); return client.JsonSerializer.Deserialize<TEvent>(webhookJson);
} }
} }
} }

View File

@@ -8,39 +8,37 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
/// <para>验证回调通知事件签名。</para> /// <para>验证回调通知事件签名。</para>
/// </summary> /// </summary>
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="callbackAuthorization"></param> /// <param name="webhookAuthorization"></param>
/// <param name="callbackBody"></param> /// <param name="webhookBody"></param>
/// <returns></returns> /// <returns></returns>
public static bool VerifyEventSignature(this WechatTenpayBusinessClient client, string callbackAuthorization, string callbackBody) public static bool VerifyEventSignature(this WechatTenpayBusinessClient client, string webhookAuthorization, string webhookBody)
{ {
return VerifyEventSignature(client, callbackAuthorization, callbackBody, out _); return VerifyEventSignature(client, webhookAuthorization, webhookBody, out _);
} }
/// <summary> /// <summary>
/// <para>验证回调通知事件签名。</para> /// <para>验证回调通知事件签名。</para>
/// </summary> /// </summary>
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="callbackAuthorization"></param> /// <param name="webhookAuthorization"></param>
/// <param name="callbackBody"></param> /// <param name="webhookBody"></param>
/// <param name="error"></param> /// <param name="error"></param>
/// <returns></returns> /// <returns></returns>
public static bool VerifyEventSignature(this WechatTenpayBusinessClient client, string callbackAuthorization, string callbackBody, out Exception? error) public static bool VerifyEventSignature(this WechatTenpayBusinessClient client, string webhookAuthorization, string webhookBody, out Exception? error)
{ {
if (client == null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
if (callbackAuthorization == null) throw new ArgumentNullException(nameof(callbackAuthorization)); if (webhookAuthorization is null) throw new ArgumentNullException(nameof(webhookAuthorization));
if (callbackBody == null) throw new ArgumentNullException(nameof(callbackBody)); if (webhookBody is null) throw new ArgumentNullException(nameof(webhookBody));
bool ret = WechatTenpayBusinessClientSignExtensions.VerifySignature( bool ret = WechatTenpayBusinessClientSigningExtensions.VerifySignature(
client, client,
strAuthorization: callbackAuthorization, strAuthorization: webhookAuthorization,
strContent: callbackBody, strContent: webhookBody,
out error out error
); );
if (error != null) if (!ret)
{ error ??= new Exception($"Failed to verify webhook event. Maybe the raw signature is invalid.");
error = new Exceptions.WechatTenpayBusinessEventVerificationException("Verify signature of event failed. Please see the inner exception for more details.", error);
}
return ret; return ret;
} }
@@ -49,22 +47,22 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
/// <para>验证回调通知事件签名。</para> /// <para>验证回调通知事件签名。</para>
/// </summary> /// </summary>
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="callbackTimestamp"></param> /// <param name="webhookTimestamp"></param>
/// <param name="callbackNonce">。</param> /// <param name="webhookNonce">。</param>
/// <param name="callbackBody"></param> /// <param name="webhookBody"></param>
/// <param name="callbackSignature"></param> /// <param name="webhookSignature"></param>
/// <param name="callbackSerialNumber"></param> /// <param name="webhookSerialNumber"></param>
/// <returns></returns> /// <returns></returns>
public static bool VerifyEventSignature(this WechatTenpayBusinessClient client, string callbackTimestamp, string callbackNonce, string callbackBody, string callbackSignature, string callbackSerialNumber) public static bool VerifyEventSignature(this WechatTenpayBusinessClient client, string webhookTimestamp, string webhookNonce, string webhookBody, string webhookSignature, string webhookSerialNumber)
{ {
return VerifyEventSignature( return VerifyEventSignature(
client, client,
callbackTimestamp: callbackTimestamp, webhookTimestamp: webhookTimestamp,
callbackNonce: callbackNonce, webhookNonce: webhookNonce,
callbackBody: callbackBody, webhookBody: webhookBody,
callbackSignature: callbackSignature, webhookSignature: webhookSignature,
callbackSignatureAlgorithm: Constants.SignAlgorithms.SHA245_WITH_RSA, webhookSignatureAlgorithm: Constants.SignAlgorithms.SHA245_WITH_RSA,
callbackSerialNumber: callbackSerialNumber, webhookSerialNumber: webhookSerialNumber,
out _ out _
); );
} }
@@ -73,23 +71,23 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
/// <para>验证回调通知事件签名。</para> /// <para>验证回调通知事件签名。</para>
/// </summary> /// </summary>
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="callbackTimestamp"></param> /// <param name="webhookTimestamp"></param>
/// <param name="callbackNonce">。</param> /// <param name="webhookNonce">。</param>
/// <param name="callbackBody"></param> /// <param name="webhookBody"></param>
/// <param name="callbackSignature"></param> /// <param name="webhookSignature"></param>
/// <param name="callbackSignatureAlgorithm"></param> /// <param name="webhookSignatureAlgorithm"></param>
/// <param name="callbackSerialNumber"></param> /// <param name="webhookSerialNumber"></param>
/// <returns></returns> /// <returns></returns>
public static bool VerifyEventSignature(this WechatTenpayBusinessClient client, string callbackTimestamp, string callbackNonce, string callbackBody, string callbackSignature, string callbackSignatureAlgorithm, string callbackSerialNumber) public static bool VerifyEventSignature(this WechatTenpayBusinessClient client, string webhookTimestamp, string webhookNonce, string webhookBody, string webhookSignature, string webhookSignatureAlgorithm, string webhookSerialNumber)
{ {
return VerifyEventSignature( return VerifyEventSignature(
client, client,
callbackTimestamp: callbackTimestamp, webhookTimestamp: webhookTimestamp,
callbackNonce: callbackNonce, webhookNonce: webhookNonce,
callbackBody: callbackBody, webhookBody: webhookBody,
callbackSignature: callbackSignature, webhookSignature: webhookSignature,
callbackSignatureAlgorithm: callbackSignatureAlgorithm, webhookSignatureAlgorithm: webhookSignatureAlgorithm,
callbackSerialNumber: callbackSerialNumber, webhookSerialNumber: webhookSerialNumber,
out _ out _
); );
} }
@@ -98,24 +96,24 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
/// <para>验证回调通知事件签名。</para> /// <para>验证回调通知事件签名。</para>
/// </summary> /// </summary>
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="callbackTimestamp"></param> /// <param name="webhookTimestamp"></param>
/// <param name="callbackNonce">。</param> /// <param name="webhookNonce">。</param>
/// <param name="callbackBody"></param> /// <param name="webhookBody"></param>
/// <param name="callbackSignature"></param> /// <param name="webhookSignature"></param>
/// <param name="callbackSerialNumber"></param> /// <param name="webhookSerialNumber"></param>
/// <param name="error"></param> /// <param name="error"></param>
/// <returns></returns> /// <returns></returns>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException"></exception>
public static bool VerifyEventSignature(this WechatTenpayBusinessClient client, string callbackTimestamp, string callbackNonce, string callbackBody, string callbackSignature, string callbackSerialNumber, out Exception? error) public static bool VerifyEventSignature(this WechatTenpayBusinessClient client, string webhookTimestamp, string webhookNonce, string webhookBody, string webhookSignature, string webhookSerialNumber, out Exception? error)
{ {
return VerifyEventSignature( return VerifyEventSignature(
client, client,
callbackTimestamp: callbackTimestamp, webhookTimestamp: webhookTimestamp,
callbackNonce: callbackNonce, webhookNonce: webhookNonce,
callbackBody: callbackBody, webhookBody: webhookBody,
callbackSignature: callbackSignature, webhookSignature: webhookSignature,
callbackSignatureAlgorithm: Constants.SignAlgorithms.SHA245_WITH_RSA, webhookSignatureAlgorithm: Constants.SignAlgorithms.SHA245_WITH_RSA,
callbackSerialNumber: callbackSerialNumber, webhookSerialNumber: webhookSerialNumber,
out error out error
); );
} }
@@ -124,34 +122,32 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
/// <para>验证回调通知事件签名。</para> /// <para>验证回调通知事件签名。</para>
/// </summary> /// </summary>
/// <param name="client"></param> /// <param name="client"></param>
/// <param name="callbackTimestamp"></param> /// <param name="webhookTimestamp"></param>
/// <param name="callbackNonce">。</param> /// <param name="webhookNonce">。</param>
/// <param name="callbackBody"></param> /// <param name="webhookBody"></param>
/// <param name="callbackSignature"></param> /// <param name="webhookSignature"></param>
/// <param name="callbackSignatureAlgorithm"></param> /// <param name="webhookSignatureAlgorithm"></param>
/// <param name="callbackSerialNumber"></param> /// <param name="webhookSerialNumber"></param>
/// <param name="error"></param> /// <param name="error"></param>
/// <returns></returns> /// <returns></returns>
/// <exception cref="ArgumentNullException"></exception> /// <exception cref="ArgumentNullException"></exception>
public static bool VerifyEventSignature(this WechatTenpayBusinessClient client, string callbackTimestamp, string callbackNonce, string callbackBody, string callbackSignature, string callbackSignatureAlgorithm, string callbackSerialNumber, out Exception? error) public static bool VerifyEventSignature(this WechatTenpayBusinessClient client, string webhookTimestamp, string webhookNonce, string webhookBody, string webhookSignature, string webhookSignatureAlgorithm, string webhookSerialNumber, out Exception? error)
{ {
if (client == null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
bool ret = WechatTenpayBusinessClientSignExtensions.VerifySignature( bool ret = WechatTenpayBusinessClientSigningExtensions.VerifySignature(
client, client,
strTimestamp: callbackTimestamp, strTimestamp: webhookTimestamp,
strNonce: callbackNonce, strNonce: webhookNonce,
strContent: callbackBody, strContent: webhookBody,
strSignature: callbackSignature, strSignature: webhookSignature,
strSignatureAlgorithm: callbackSignatureAlgorithm, strSignatureAlgorithm: webhookSignatureAlgorithm,
strSerialNumber: callbackSerialNumber, strSerialNumber: webhookSerialNumber,
out error out error
); );
if (error != null) if (!ret)
{ error ??= new Exception($"Failed to verify webhook event. Maybe the raw signature \"{webhookSignature}\" is invalid.");
error = new Exceptions.WechatTenpayBusinessEventVerificationException("Verify signature of event failed. Please see the inner exception for more details.", error);
}
return ret; return ret;
} }

View File

@@ -22,9 +22,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "mse-pay", "bill-downloads"); .CreateFlurlRequest(request, HttpMethod.Post, "mse-pay", "bill-downloads");
return await client.SendRequestWithJsonAsync<Models.GetBillResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.GetBillResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -41,9 +41,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "mse-pay", "bill-downloads", "trans"); .CreateFlurlRequest(request, HttpMethod.Post, "mse-pay", "bill-downloads", "trans");
return await client.SendRequestWithJsonAsync<Models.GetBillTransactionResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.GetBillTransactionResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -62,10 +62,10 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Get, request.DownloadUrl) .CreateFlurlRequest(request, HttpMethod.Get, request.DownloadUrl)
.WithUrl(request.DownloadUrl); .WithUrl(request.DownloadUrl);
return await client.SendRequestWithJsonAsync<Models.DownloadBillFileResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.DownloadBillFileResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
} }
} }

View File

@@ -21,20 +21,20 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (client is null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
if (request.FileName == null) if (request.FileName is null)
request.FileName = Guid.NewGuid().ToString("N").ToLower() + ".png"; request.FileName = Guid.NewGuid().ToString("N").ToLower() + ".png";
if (request.FileHash == null) if (request.FileHash is null)
request.FileHash = BitConverter.ToString(Utilities.SM3Utility.Hash(request.FileBytes)).Replace("-", string.Empty).ToLower(); request.FileHash = BitConverter.ToString(Utilities.SM3Utility.Hash(request.FileBytes)).Replace("-", string.Empty).ToLower();
if (request.FileContentType == null) if (request.FileContentType is null)
request.FileContentType = Utilities.FileNameToContentTypeMapper.GetContentTypeForImage(request.FileName!) ?? "image/png"; request.FileContentType = Utilities.FileNameToContentTypeMapper.GetContentTypeForImage(request.FileName!) ?? "image/png";
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "mse-pay", "file-uploads"); .CreateFlurlRequest(request, HttpMethod.Post, "mse-pay", "file-uploads");
using var httpContent = Utilities.FileHttpContentBuilder.Build(fileName: request.FileName, fileBytes: request.FileBytes, fileContentType: request.FileContentType, fileMetaJson: client.JsonSerializer.Serialize(request)); using var httpContent = Utilities.FileHttpContentBuilder.Build(fileName: request.FileName, fileBytes: request.FileBytes, fileContentType: request.FileContentType, fileMetaJson: client.JsonSerializer.Serialize(request));
return await client.SendRequestAsync<Models.UploadFileResponse>(flurlReq, httpContent: httpContent, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsync<Models.UploadFileResponse>(flurlReq, httpContent: httpContent, cancellationToken: cancellationToken);
} }
} }
} }

View File

@@ -23,9 +23,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "mse-pay", "payments", "app-pay"); .CreateFlurlRequest(request, HttpMethod.Post, "mse-pay", "payments", "app-pay");
return await client.SendRequestWithJsonAsync<Models.CreatePaymentAppPayResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.CreatePaymentAppPayResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -42,9 +42,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "mse-pay", "payments", "app-registering-pay"); .CreateFlurlRequest(request, HttpMethod.Post, "mse-pay", "payments", "app-registering-pay");
return await client.SendRequestWithJsonAsync<Models.CreatePaymentAppRegisteringPayResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.CreatePaymentAppRegisteringPayResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -61,9 +61,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "mse-pay", "payments", "qrcode-pay"); .CreateFlurlRequest(request, HttpMethod.Post, "mse-pay", "payments", "qrcode-pay");
return await client.SendRequestWithJsonAsync<Models.CreatePaymentQrcodePayResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.CreatePaymentQrcodePayResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -80,9 +80,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "mse-pay", "payments", "h5-pay"); .CreateFlurlRequest(request, HttpMethod.Post, "mse-pay", "payments", "h5-pay");
return await client.SendRequestWithJsonAsync<Models.CreatePaymentH5PayResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.CreatePaymentH5PayResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -99,9 +99,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "mse-pay", "payments", "mp-pay"); .CreateFlurlRequest(request, HttpMethod.Post, "mse-pay", "payments", "mp-pay");
return await client.SendRequestWithJsonAsync<Models.CreatePaymentMiniProgramPayResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.CreatePaymentMiniProgramPayResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -121,9 +121,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Get, "mse-pay", "payments", request.PaymentId); .CreateFlurlRequest(request, HttpMethod.Get, "mse-pay", "payments", request.PaymentId);
return await client.SendRequestWithJsonAsync<Models.GetPaymentByPaymentIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.GetPaymentByPaymentIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -143,9 +143,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Get, "mse-pay", "payments", "out-payment-id", request.OutPaymentId); .CreateFlurlRequest(request, HttpMethod.Get, "mse-pay", "payments", "out-payment-id", request.OutPaymentId);
return await client.SendRequestWithJsonAsync<Models.GetPaymentByOutPaymentIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.GetPaymentByOutPaymentIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -165,9 +165,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "mse-pay", "payments", request.PaymentId, "close"); .CreateFlurlRequest(request, HttpMethod.Post, "mse-pay", "payments", request.PaymentId, "close");
return await client.SendRequestWithJsonAsync<Models.ClosePaymentResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.ClosePaymentResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -187,9 +187,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "mse-pay", "redirects"); .CreateFlurlRequest(request, HttpMethod.Post, "mse-pay", "redirects");
return await client.SendRequestWithJsonAsync<Models.CreatePaymentRedirectLinkResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.CreatePaymentRedirectLinkResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
} }
} }

View File

@@ -22,9 +22,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "mse-pay", "product-applications"); .CreateFlurlRequest(request, HttpMethod.Post, "mse-pay", "product-applications");
return await client.SendRequestWithJsonAsync<Models.CreateProductApplicationResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.CreateProductApplicationResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -41,9 +41,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Get, "mse-pay", "product-applications", request.RequestNumber); .CreateFlurlRequest(request, HttpMethod.Get, "mse-pay", "product-applications", request.RequestNumber);
return await client.SendRequestWithJsonAsync<Models.GetProductApplicationByRequestNumberResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.GetProductApplicationByRequestNumberResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -60,9 +60,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Get, "mse-pay", "product-applications", "out-request-no", request.OutRequestNumber); .CreateFlurlRequest(request, HttpMethod.Get, "mse-pay", "product-applications", "out-request-no", request.OutRequestNumber);
return await client.SendRequestWithJsonAsync<Models.GetProductApplicationByOutRequestNumberResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.GetProductApplicationByOutRequestNumberResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -79,9 +79,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "mse-pay", "product-applications", request.RequestNumber, "links"); .CreateFlurlRequest(request, HttpMethod.Post, "mse-pay", "product-applications", request.RequestNumber, "links");
return await client.SendRequestWithJsonAsync<Models.CreateProductApplicationLinkResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.CreateProductApplicationLinkResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
} }
} }

View File

@@ -21,13 +21,13 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (client is null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
if (request.EnterpriseId == null) if (request.EnterpriseId is null)
request.EnterpriseId = client.Credentials.EnterpriseId; request.EnterpriseId = client.Credentials.EnterpriseId;
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "mse-pay", "profit-allocations"); .CreateFlurlRequest(request, HttpMethod.Post, "mse-pay", "profit-allocations");
return await client.SendRequestWithJsonAsync<Models.CreateProfitAllocationResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.CreateProfitAllocationResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -43,13 +43,13 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (client is null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
if (request.EnterpriseId == null) if (request.EnterpriseId is null)
request.EnterpriseId = client.Credentials.EnterpriseId; request.EnterpriseId = client.Credentials.EnterpriseId;
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "mse-pay", "profit-allocations", "finish"); .CreateFlurlRequest(request, HttpMethod.Post, "mse-pay", "profit-allocations", "finish");
return await client.SendRequestWithJsonAsync<Models.SetProfitAllocationFinishedResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.SetProfitAllocationFinishedResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -66,9 +66,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Get, "mse-pay", "profit-allocations", request.AllocationId); .CreateFlurlRequest(request, HttpMethod.Get, "mse-pay", "profit-allocations", request.AllocationId);
return await client.SendRequestWithJsonAsync<Models.GetProfitAllocationByAllocationIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.GetProfitAllocationByAllocationIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -85,9 +85,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Get, "mse-pay", "profit-allocations", "out-allocation-id", request.OutAllocationId); .CreateFlurlRequest(request, HttpMethod.Get, "mse-pay", "profit-allocations", "out-allocation-id", request.OutAllocationId);
return await client.SendRequestWithJsonAsync<Models.GetProfitAllocationByOutAllocationIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.GetProfitAllocationByOutAllocationIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -104,9 +104,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Get, "mse-pay", "profit-allocations", request.PaymentId, "amounts"); .CreateFlurlRequest(request, HttpMethod.Get, "mse-pay", "profit-allocations", request.PaymentId, "amounts");
return await client.SendRequestWithJsonAsync<Models.GetProfitAllocationAmountByPaymentIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.GetProfitAllocationAmountByPaymentIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
#region ReceiverAccount #region ReceiverAccount
@@ -123,18 +123,18 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (client is null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
if (request.EnterpriseId == null) if (request.EnterpriseId is null)
request.EnterpriseId = client.Credentials.EnterpriseId; request.EnterpriseId = client.Credentials.EnterpriseId;
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Get, "mse-pay", "profit-allocations", "receiver-accounts") .CreateFlurlRequest(request, HttpMethod.Get, "mse-pay", "profit-allocations", "receiver-accounts")
.SetQueryParam("ent_id", request.EnterpriseId); .SetQueryParam("ent_id", request.EnterpriseId);
if (request.UnifiedSocialCreditCode != null) if (request.UnifiedSocialCreditCode is not null)
flurlReq.SetQueryParam("unified_social_credit_code", request.UnifiedSocialCreditCode); flurlReq.SetQueryParam("unified_social_credit_code", request.UnifiedSocialCreditCode);
return await client.SendRequestWithJsonAsync<Models.QueryProfitAllocationReceiverAccountsResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.QueryProfitAllocationReceiverAccountsResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -150,13 +150,13 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (client is null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
if (request.EnterpriseId == null) if (request.EnterpriseId is null)
request.EnterpriseId = client.Credentials.EnterpriseId; request.EnterpriseId = client.Credentials.EnterpriseId;
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "mse-pay", "profit-allocations", "receiver-accounts-applications"); .CreateFlurlRequest(request, HttpMethod.Post, "mse-pay", "profit-allocations", "receiver-accounts-applications");
return await client.SendRequestWithJsonAsync<Models.CreateProfitAllocationReceiverAccountApplicationResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.CreateProfitAllocationReceiverAccountApplicationResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -173,9 +173,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Get, "mse-pay", "profit-allocations", "receiver-accounts-applications", request.ApplicationId); .CreateFlurlRequest(request, HttpMethod.Get, "mse-pay", "profit-allocations", "receiver-accounts-applications", request.ApplicationId);
return await client.SendRequestWithJsonAsync<Models.GetProfitAllocationReceiverAccountApplicationByApplicationIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.GetProfitAllocationReceiverAccountApplicationByApplicationIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -192,9 +192,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Get, "mse-pay", "profit-allocations", "receiver-accounts-applications", "out_application_id", request.OutApplicationId); .CreateFlurlRequest(request, HttpMethod.Get, "mse-pay", "profit-allocations", "receiver-accounts-applications", "out_application_id", request.OutApplicationId);
return await client.SendRequestWithJsonAsync<Models.GetProfitAllocationReceiverAccountApplicationByOutApplicationIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.GetProfitAllocationReceiverAccountApplicationByOutApplicationIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
#endregion #endregion
} }

View File

@@ -22,9 +22,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "mse-pay", "refunds"); .CreateFlurlRequest(request, HttpMethod.Post, "mse-pay", "refunds");
return await client.SendRequestWithJsonAsync<Models.CreateRefundResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.CreateRefundResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -41,9 +41,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Get, "mse-pay", "refunds", "refund-id", request.RefundId); .CreateFlurlRequest(request, HttpMethod.Get, "mse-pay", "refunds", "refund-id", request.RefundId);
return await client.SendRequestWithJsonAsync<Models.GetRefundByRefundIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.GetRefundByRefundIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -60,9 +60,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Get, "mse-pay", "refunds", "out-refund-id", request.OutRefundId); .CreateFlurlRequest(request, HttpMethod.Get, "mse-pay", "refunds", "out-refund-id", request.OutRefundId);
return await client.SendRequestWithJsonAsync<Models.GetRefundByOutRefundIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.GetRefundByOutRefundIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
} }
} }

View File

@@ -21,13 +21,13 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (client is null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
if (request.EnterpriseId == null) if (request.EnterpriseId is null)
request.EnterpriseId = client.Credentials.EnterpriseId; request.EnterpriseId = client.Credentials.EnterpriseId;
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Post, "mse-pay", "withdraws"); .CreateFlurlRequest(request, HttpMethod.Post, "mse-pay", "withdraws");
return await client.SendRequestWithJsonAsync<Models.CreateWithdrawResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.CreateWithdrawResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -43,9 +43,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Get, "mse-pay", "withdraws", request.WithdrawId); .CreateFlurlRequest(request, HttpMethod.Get, "mse-pay", "withdraws", request.WithdrawId);
return await client.SendRequestWithJsonAsync<Models.GetWithdrawByWithdrawIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.GetWithdrawByWithdrawIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
/// <summary> /// <summary>
@@ -61,9 +61,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
if (request is null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
IFlurlRequest flurlReq = client IFlurlRequest flurlReq = client
.CreateRequest(request, HttpMethod.Get, "mse-pay", "withdraws", "out-withdraw-id", request.OutWithdrawId); .CreateFlurlRequest(request, HttpMethod.Get, "mse-pay", "withdraws", "out-withdraw-id", request.OutWithdrawId);
return await client.SendRequestWithJsonAsync<Models.GetWithdrawByOutWithdrawIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken); return await client.SendFlurlRequestAsJsonAsync<Models.GetWithdrawByOutWithdrawIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
} }
} }
} }

View File

@@ -15,32 +15,32 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
public static TRequest EncryptRequestSensitiveProperty<TRequest>(this WechatTenpayBusinessClient client, TRequest request) public static TRequest EncryptRequestSensitiveProperty<TRequest>(this WechatTenpayBusinessClient client, TRequest request)
where TRequest : WechatTenpayBusinessRequest where TRequest : WechatTenpayBusinessRequest
{ {
if (client == null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
if (request == null) throw new ArgumentNullException(nameof(request)); if (request is null) throw new ArgumentNullException(nameof(request));
try try
{ {
bool requireEncrypt = request.GetType().GetCustomAttributes<WechatTenpayBusinessSensitiveAttribute>(inherit: true).Any(); bool requireEncrypt = request.GetType().GetCustomAttributes<WechatTenpayBusinessSensitiveAttribute>(inherit: true).Any();
if (requireEncrypt) if (requireEncrypt)
{ {
if (request.Encryption is null) if (request.WechatpayEncryption is null)
request.Encryption = new WechatTenpayBusinessRequestEncryption() { Algorithm = client.Credentials.SensitivePropertyEncryptionAlgorithm }; request.WechatpayEncryption = new WechatTenpayBusinessRequestEncryption() { Algorithm = client.Credentials.SensitivePropertyEncryptionAlgorithm };
if (Constants.EncryptionAlgorithms.RSA_OAEP_WITH_SM4_128_CBC.Equals(request.Encryption.Algorithm)) if (Constants.EncryptionAlgorithms.RSA_OAEP_WITH_SM4_128_CBC.Equals(request.WechatpayEncryption.Algorithm))
{ {
Utilities.ReflectionUtility.ReplacePropertyStringValue(ref request, (target, currentProp, oldValue) => Utilities.ReflectionUtility.ReplacePropertyStringValue(ref request, (target, currentProp, oldValue) =>
{ {
var attr = currentProp.GetCustomAttribute<WechatTenpayBusinessSensitivePropertyAttribute>(); var attr = currentProp.GetCustomAttribute<WechatTenpayBusinessSensitivePropertyAttribute>();
if (attr == null) if (attr is null)
return (false, oldValue); return (false, oldValue);
string sm4IV = client.Credentials.SensitivePropertyEncryptionSM4IV!; string sm4IV = client.Credentials.SensitivePropertyEncryptionSM4IV!;
string sm4Key = client.Credentials.SensitivePropertyEncryptionSM4Key!; string sm4Key = client.Credentials.SensitivePropertyEncryptionSM4Key!;
string sm4EncryptedKey = Utilities.RSAUtility.EncryptWithECB(publicKey: client.Credentials.TBEPCertificatePublicKey, plainText: sm4Key); string sm4EncryptedKey = Utilities.RSAUtility.EncryptWithECB(publicKey: client.Credentials.TBEPCertificatePublicKey, plainText: sm4Key);
request.Encryption.SerialNumber = client.Credentials.TBEPCertificateSerialNumber; request.WechatpayEncryption.SerialNumber = client.Credentials.TBEPCertificateSerialNumber;
request.Encryption.EncryptedKey = sm4EncryptedKey; request.WechatpayEncryption.EncryptedKey = sm4EncryptedKey;
request.Encryption.IV = sm4IV; request.WechatpayEncryption.IV = sm4IV;
string newValue = Utilities.SM4Utility.EncryptWithCBC(key: sm4Key, iv: sm4IV, plainText: oldValue); string newValue = Utilities.SM4Utility.EncryptWithCBC(key: sm4Key, iv: sm4IV, plainText: oldValue);
return (true, newValue); return (true, newValue);
@@ -48,13 +48,17 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
} }
else else
{ {
throw new NotSupportedException("Unsupported encryption algorithm."); throw new WechatTenpayBusinessException($"Failed to encrypt request. Unsupported encryption algorithm: \"{request.WechatpayEncryption.Algorithm}\".");
} }
} }
} }
catch (Exception ex) when (!(ex is Exceptions.WechatTenpayBusinessRequestEncryptionException)) catch (WechatTenpayBusinessException)
{ {
throw new Exceptions.WechatTenpayBusinessRequestEncryptionException("Failed to encrypt request. Please see the inner exception for more details.", ex); throw;
}
catch (Exception ex)
{
throw new WechatTenpayBusinessException("Failed to encrypt request. Please see the inner exception for more details.", ex);
} }
return request; return request;

View File

@@ -15,40 +15,40 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
public static TResponse DecryptResponseSensitiveProperty<TResponse>(this WechatTenpayBusinessClient client, TResponse response) public static TResponse DecryptResponseSensitiveProperty<TResponse>(this WechatTenpayBusinessClient client, TResponse response)
where TResponse : WechatTenpayBusinessResponse where TResponse : WechatTenpayBusinessResponse
{ {
if (client == null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
if (response == null) throw new ArgumentNullException(nameof(response)); if (response is null) throw new ArgumentNullException(nameof(response));
if (!response.IsSuccessful()) if (!response.IsSuccessful())
throw new Exceptions.WechatTenpayBusinessResponseDecryptionException("Failed to decrypt response, because the response is not successful."); throw new WechatTenpayBusinessException("Failed to decrypt response, because the response is not successful.");
try try
{ {
bool requireDecrypt = response.GetType().GetCustomAttributes<WechatTenpayBusinessSensitiveAttribute>(inherit: true).Any(); bool requireDecrypt = response.GetType().GetCustomAttributes<WechatTenpayBusinessSensitiveAttribute>(inherit: true).Any();
if (requireDecrypt) if (requireDecrypt)
{ {
if (response.Encryption is null) if (response.WechatpayEncryption is null)
throw new InvalidOperationException("Could not read value of 'TBEP-Encrypt'."); throw new InvalidOperationException("Failed to decrypt response, because the value of \"TBEP-Encrypt\" is empty.");
if (response.Encryption.PlatformId != null && response.Encryption.SerialNumber != client.Credentials.PlatformCertificateSerialNumber) if (response.WechatpayEncryption.PlatformId is not null && response.WechatpayEncryption.SerialNumber != client.Credentials.PlatformCertificateSerialNumber)
throw new Exceptions.WechatTenpayBusinessResponseDecryptionException("Failed to decrypt response, because the platform certificate serial number does not match."); throw new WechatTenpayBusinessException($"Failed to decrypt response, because the platform certificate with serial number \"{response.WechatpayEncryption.SerialNumber}\" does not existed.");
if (response.Encryption.EnterpriseId != null && response.Encryption.SerialNumber != client.Credentials.EnterpriseCertificateSerialNumber) if (response.WechatpayEncryption.EnterpriseId is not null && response.WechatpayEncryption.SerialNumber != client.Credentials.EnterpriseCertificateSerialNumber)
throw new Exceptions.WechatTenpayBusinessResponseDecryptionException("Failed to decrypt response, because the enterprise certificate serial number does not match."); throw new WechatTenpayBusinessException($"Failed to decrypt response, because the enterprise certificate serial number \"{response.WechatpayEncryption.SerialNumber}\" does not existed.");
if (Constants.EncryptionAlgorithms.RSA_OAEP_WITH_SM4_128_CBC.Equals(response.Encryption.Algorithm)) if (Constants.EncryptionAlgorithms.RSA_OAEP_WITH_SM4_128_CBC.Equals(response.WechatpayEncryption.Algorithm))
{ {
Utilities.ReflectionUtility.ReplacePropertyStringValue(ref response, (target, currentProp, oldValue) => Utilities.ReflectionUtility.ReplacePropertyStringValue(ref response, (target, currentProp, oldValue) =>
{ {
var attr = currentProp.GetCustomAttribute<WechatTenpayBusinessSensitivePropertyAttribute>(); var attr = currentProp.GetCustomAttribute<WechatTenpayBusinessSensitivePropertyAttribute>();
if (attr == null) if (attr is null)
return (false, oldValue); return (false, oldValue);
string sm4EncryptedKey = response.Encryption.EncryptedKey!; string sm4EncryptedKey = response.WechatpayEncryption.EncryptedKey!;
string sm4Key = Utilities.RSAUtility.DecryptWithECB( string sm4Key = Utilities.RSAUtility.DecryptWithECB(
privateKey: response.Encryption.PlatformId != null ? client.Credentials.PlatformCertificatePrivateKey! : privateKey: response.WechatpayEncryption.PlatformId is not null ? client.Credentials.PlatformCertificatePrivateKey! :
response.Encryption.EnterpriseId != null ? client.Credentials.EnterpriseCertificatePrivateKey! : response.WechatpayEncryption.EnterpriseId is not null ? client.Credentials.EnterpriseCertificatePrivateKey! :
string.Empty, string.Empty,
cipherText: sm4EncryptedKey cipherText: sm4EncryptedKey
); );
string sm4IV = response.Encryption.IV!; string sm4IV = response.WechatpayEncryption.IV!;
string newValue = Utilities.SM4Utility.DecryptWithCBC(key: sm4Key, iv: sm4IV, cipherText: oldValue); string newValue = Utilities.SM4Utility.DecryptWithCBC(key: sm4Key, iv: sm4IV, cipherText: oldValue);
return (true, newValue); return (true, newValue);
@@ -56,13 +56,17 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
} }
else else
{ {
throw new NotSupportedException("Unsupported decryption algorithm."); throw new WechatTenpayBusinessException($"Failed to decrypt response. Unsupported encryption algorithm: \"{response.WechatpayEncryption.Algorithm}\".");
} }
} }
} }
catch (Exception ex) when (!(ex is Exceptions.WechatTenpayBusinessResponseDecryptionException)) catch (WechatTenpayBusinessException)
{ {
throw new Exceptions.WechatTenpayBusinessResponseDecryptionException("Failed to decrypt response. Please see the inner exception for more details.", ex); throw;
}
catch (Exception ex)
{
throw new WechatTenpayBusinessException("Failed to decrypt response. Please see the inner exception for more details.", ex);
} }
return response; return response;

View File

@@ -30,18 +30,18 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
public static bool VerifyResponseSignature<TResponse>(this WechatTenpayBusinessClient client, TResponse response, out Exception? error) public static bool VerifyResponseSignature<TResponse>(this WechatTenpayBusinessClient client, TResponse response, out Exception? error)
where TResponse : WechatTenpayBusinessResponse where TResponse : WechatTenpayBusinessResponse
{ {
if (client == null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
bool ret = WechatTenpayBusinessClientSignExtensions.VerifySignature( bool ret = WechatTenpayBusinessClientSigningExtensions.VerifySignature(
client, client,
strAuthorization: response.RawHeaders.FirstOrDefault(e => string.Equals(e.Key, "TBEP-Authorization", StringComparison.OrdinalIgnoreCase)).Value, strAuthorization: response.GetRawHeaders().GetFirstValueOrEmpty("TBEP-Authorization"),
strContent: Encoding.UTF8.GetString(response.RawBytes), strContent: Encoding.UTF8.GetString(response.GetRawBytes()),
out error out error
); );
if (error != null) if (error is not null)
{ {
error = new Exceptions.WechatTenpayBusinessResponseVerificationException("Verify signature of response failed. Please see the inner exception for more details.", error); error = new WechatTenpayBusinessException("Verify signature of response failed. Please see the inner exception for more details.", error);
} }
return ret; return ret;
@@ -135,9 +135,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
/// <returns></returns> /// <returns></returns>
public static bool VerifyResponseSignature(this WechatTenpayBusinessClient client, string responseTimestamp, string responseNonce, string responseBody, string responseSignature, string responseSignatureAlgorithm, string responseSerialNumber, out Exception? error) public static bool VerifyResponseSignature(this WechatTenpayBusinessClient client, string responseTimestamp, string responseNonce, string responseBody, string responseSignature, string responseSignatureAlgorithm, string responseSerialNumber, out Exception? error)
{ {
if (client == null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
bool ret = WechatTenpayBusinessClientSignExtensions.VerifySignature( bool ret = WechatTenpayBusinessClientSigningExtensions.VerifySignature(
client, client,
strTimestamp: responseTimestamp, strTimestamp: responseTimestamp,
strNonce: responseNonce, strNonce: responseNonce,
@@ -148,10 +148,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
out error out error
); );
if (error != null) if (!ret)
{ error ??= new Exception($"Failed to verify response. Maybe the raw signature \"{responseSignature}\" is invalid.");
error = new Exceptions.WechatTenpayBusinessResponseVerificationException("Verify signature of response failed. Please see the inner exception for more details.", error);
}
return ret; return ret;
} }

View File

@@ -4,7 +4,7 @@ using System.Linq;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
{ {
internal static class WechatTenpayBusinessClientSignExtensions internal static class WechatTenpayBusinessClientSigningExtensions
{ {
public static bool VerifySignature(this WechatTenpayBusinessClient client, string strAuthorization, string strContent, out Exception? error) public static bool VerifySignature(this WechatTenpayBusinessClient client, string strAuthorization, string strContent, out Exception? error)
{ {
@@ -43,26 +43,26 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
} }
} }
error = new Exception("Could not read value of 'TBEP-Authorization'."); error = new Exception("The value of \"TBEP-Authorization\" is empty.");
return false; return false;
} }
public static bool VerifySignature(this WechatTenpayBusinessClient client, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignatureAlgorithm, string strSerialNumber, out Exception? error) public static bool VerifySignature(this WechatTenpayBusinessClient client, string strTimestamp, string strNonce, string strContent, string strSignature, string strSignatureAlgorithm, string strSerialNumber, out Exception? error)
{ {
if (client == null) throw new ArgumentNullException(nameof(client)); if (client is null) throw new ArgumentNullException(nameof(client));
switch (strSignatureAlgorithm) switch (strSignatureAlgorithm)
{ {
case Constants.SignAlgorithms.SHA245_WITH_RSA: case Constants.SignAlgorithms.SHA245_WITH_RSA:
{ {
if (client.Credentials.TBEPCertificateSerialNumber != null && if (client.Credentials.TBEPCertificateSerialNumber is not null &&
client.Credentials.TBEPCertificatePublicKey != null) client.Credentials.TBEPCertificatePublicKey is not null)
{ {
try try
{ {
if (!string.Equals(client.Credentials.TBEPCertificateSerialNumber, strSerialNumber)) if (!string.Equals(client.Credentials.TBEPCertificateSerialNumber, strSerialNumber))
{ {
error = new Exception("There is no TBEP public key matched the serial number."); error = new Exception($"There is no TBEP public key matched the serial number \"{strSerialNumber}\".");
return false; return false;
} }
@@ -86,7 +86,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
default: default:
{ {
error = new Exception("Unsupported sign algorithm."); error = new Exception($"Unsupported sign algorithm: \"{strSignatureAlgorithm}\".");
return false; return false;
} }
} }

View File

@@ -1,14 +1,13 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Flurl.Http; using Flurl.Http;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Interceptors namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Interceptors
{ {
using SKIT.FlurlHttpClient.Constants; internal class WechatTenpayBusinessRequestSigningInterceptor : HttpInterceptor
internal class WechatTenpayBusinessRequestSignatureInterceptor : FlurlHttpCallInterceptor
{ {
private const string HTTP_HEADER_PLATFORM_AUTHORIZATION = HttpHeaders.Authorization; private const string HTTP_HEADER_PLATFORM_AUTHORIZATION = HttpHeaders.Authorization;
private const string HTTP_HEADER_ENTERPRISE_AUTHORIZATION = "Enterprise-Authorization"; private const string HTTP_HEADER_ENTERPRISE_AUTHORIZATION = "Enterprise-Authorization";
@@ -21,7 +20,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Interceptors
private readonly string? _enterpriseCertSn; private readonly string? _enterpriseCertSn;
private readonly string? _enterpriseCertPk; private readonly string? _enterpriseCertPk;
public WechatTenpayBusinessRequestSignatureInterceptor(string signAlg, string platformId, string platformCertSn, string platformCertPk, string? enterpriseId, string? enterpriseCertSn, string? enterpriseCertPk) public WechatTenpayBusinessRequestSigningInterceptor(string signAlg, string platformId, string platformCertSn, string platformCertPk, string? enterpriseId, string? enterpriseCertSn, string? enterpriseCertPk)
{ {
_signAlg = signAlg; _signAlg = signAlg;
_platformId = platformId; _platformId = platformId;
@@ -32,35 +31,35 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Interceptors
_enterpriseCertPk = enterpriseCertPk; _enterpriseCertPk = enterpriseCertPk;
} }
public override async Task BeforeCallAsync(FlurlCall flurlCall) public override async Task BeforeCallAsync(HttpInterceptorContext context, CancellationToken cancellationToken = default)
{ {
if (flurlCall == null) throw new ArgumentNullException(nameof(flurlCall)); if (context is null) throw new ArgumentNullException(nameof(context));
if (flurlCall.Completed) throw new Exceptions.WechatTenpayBusinessRequestSignatureException("This interceptor must be called before request completed."); if (context.FlurlCall.Completed) throw new WechatTenpayBusinessException("Failed to sign request. This interceptor must be called before request completed.");
string method = flurlCall.HttpRequestMessage.Method.ToString().ToUpper(); string method = context.FlurlCall.HttpRequestMessage.Method.ToString().ToUpper();
string url = flurlCall.HttpRequestMessage.RequestUri?.PathAndQuery ?? string.Empty; string url = context.FlurlCall.HttpRequestMessage.RequestUri?.PathAndQuery ?? string.Empty;
string timestamp = DateTimeOffset.Now.ToLocalTime().ToUnixTimeSeconds().ToString(); string timestamp = DateTimeOffset.Now.ToLocalTime().ToUnixTimeSeconds().ToString();
string nonce = Guid.NewGuid().ToString("N"); string nonce = Guid.NewGuid().ToString("N");
string body = string.Empty; string body = string.Empty;
if (flurlCall.HttpRequestMessage.Content is MultipartFormDataContent formdataContent) if (context.FlurlCall.HttpRequestMessage.Content is MultipartFormDataContent formdataContent)
{ {
// NOTICE: multipart/form-data 文件上传请求的待签名参数需特殊处理 // NOTICE: multipart/form-data 文件上传请求的待签名参数需特殊处理
var httpContent = formdataContent.SingleOrDefault(e => Constants.FormDataFields.FORMDATA_META.Equals(e.Headers.ContentDisposition?.Name?.Trim('\"'))); var httpContent = formdataContent.SingleOrDefault(e => Constants.FormDataFields.FORMDATA_META.Equals(e.Headers.ContentDisposition?.Name?.Trim('\"')));
if (httpContent != null) if (httpContent is not null)
{ {
body = await httpContent.ReadAsStringAsync(); body = await httpContent.ReadAsStringAsync();
} }
} }
else else
{ {
body = flurlCall.RequestBody ?? string.Empty; body = context.FlurlCall.RequestBody ?? string.Empty;
} }
string plainText = $"{method}\n{url}\n{timestamp}\n{nonce}\n{body}\n"; string plainText = $"{method}\n{url}\n{timestamp}\n{nonce}\n{body}\n";
string signText; string signText;
bool softSignRequired = _enterpriseId != null && _enterpriseCertSn != null && _enterpriseCertPk != null; bool softSignRequired = _enterpriseId is not null && _enterpriseCertSn is not null && _enterpriseCertPk is not null;
string? softSignText = null; string? softSignText = null;
switch (_signAlg) switch (_signAlg)
@@ -73,31 +72,27 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Interceptors
if (softSignRequired) if (softSignRequired)
{ {
byte[] keyBytes = Convert.FromBase64String(_enterpriseCertPk); byte[] keyBytes = Convert.FromBase64String(_enterpriseCertPk!);
byte[] msgBytes = Convert.FromBase64String(signText); byte[] msgBytes = Convert.FromBase64String(signText);
softSignText = Convert.ToBase64String(Utilities.RSAUtility.SignWithSHA256(keyBytes, msgBytes)); softSignText = Convert.ToBase64String(Utilities.RSAUtility.SignWithSHA256(keyBytes, msgBytes));
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
throw new Exceptions.WechatTenpayBusinessRequestSignatureException("Failed to generate signature of request. Please see the inner exception for more details.", ex); throw new WechatTenpayBusinessException("Failed to sign request. Please see the inner exception for more details.", ex);
} }
} }
break; break;
default: default:
throw new Exceptions.WechatTenpayBusinessRequestSignatureException("Unsupported authorization sign algorithm."); throw new WechatTenpayBusinessException($"Failed to sign request. Unsupported signing algorithm: \"{_signAlg}\".");
} }
string authString = $"platform_id=\"{_platformId}\",platform_serial_number=\"{_platformCertSn}\",nonce=\"{nonce}\",timestamp=\"{timestamp}\",signature=\"{signText}\",signature_algorithm=\"{_signAlg}\""; context.FlurlCall.Request.WithHeader(HTTP_HEADER_PLATFORM_AUTHORIZATION, $"platform_id=\"{_platformId}\",platform_serial_number=\"{_platformCertSn}\",nonce=\"{nonce}\",timestamp=\"{timestamp}\",signature=\"{signText}\",signature_algorithm=\"{_signAlg}\"");
flurlCall.Request.Headers.Remove(HTTP_HEADER_PLATFORM_AUTHORIZATION);
flurlCall.Request.WithHeader(HttpHeaders.Authorization, authString);
if (softSignRequired) if (softSignRequired)
{ {
string softAuthString = $"ent_id=\"{_enterpriseId}\",enterprise_serial_number=\"{_enterpriseCertSn}\",signature=\"{softSignText}\",signature_algorithm=\"{_signAlg}\""; context.FlurlCall.Request.WithHeader(HTTP_HEADER_ENTERPRISE_AUTHORIZATION, $"ent_id=\"{_enterpriseId}\",enterprise_serial_number=\"{_enterpriseCertSn}\",signature=\"{softSignText}\",signature_algorithm=\"{_signAlg}\"");
flurlCall.Request.Headers.Remove(HTTP_HEADER_ENTERPRISE_AUTHORIZATION);
flurlCall.Request.WithHeader(HTTP_HEADER_ENTERPRISE_AUTHORIZATION, softAuthString);
} }
} }
} }

View File

@@ -1,3 +1,5 @@
using System.Linq;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
{ {
/// <summary> /// <summary>
@@ -7,7 +9,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
{ {
public override bool IsSuccessful() public override bool IsSuccessful()
{ {
return base.IsSuccessful() && RawBytes?.Length > 0; return base.IsSuccessful() && GetRawBytes().Any();
} }
} }
} }

View File

@@ -32,9 +32,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
/// 获取或设置关单时间。 /// 获取或设置关单时间。
/// </summary> /// </summary>
[Newtonsoft.Json.JsonProperty("close_time")] [Newtonsoft.Json.JsonProperty("close_time")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339DateTimeOffsetConverter))] [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.Rfc3339DateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("close_time")] [System.Text.Json.Serialization.JsonPropertyName("close_time")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339DateTimeOffsetConverter))] [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.Rfc3339DateTimeOffsetConverter))]
public DateTimeOffset CloseTime { get; set; } public DateTimeOffset CloseTime { get; set; }
} }
} }

View File

@@ -163,9 +163,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
/// 获取或设置下单时间。 /// 获取或设置下单时间。
/// </summary> /// </summary>
[Newtonsoft.Json.JsonProperty("create_time")] [Newtonsoft.Json.JsonProperty("create_time")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339NullableDateTimeOffsetConverter))] [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.Rfc3339DateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("create_time")] [System.Text.Json.Serialization.JsonPropertyName("create_time")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339NullableDateTimeOffsetConverter))] [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.Rfc3339DateTimeOffsetConverter))]
public DateTimeOffset? CreateTime { get; set; } public DateTimeOffset? CreateTime { get; set; }
/// <summary> /// <summary>
@@ -254,9 +254,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
/// 获取或设置过期时间。 /// 获取或设置过期时间。
/// </summary> /// </summary>
[Newtonsoft.Json.JsonProperty("expire_time")] [Newtonsoft.Json.JsonProperty("expire_time")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339DateTimeOffsetConverter))] [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.Rfc3339DateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("expire_time")] [System.Text.Json.Serialization.JsonPropertyName("expire_time")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339DateTimeOffsetConverter))] [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.Rfc3339DateTimeOffsetConverter))]
public DateTimeOffset ExpireTime { get; set; } public DateTimeOffset ExpireTime { get; set; }
/// <summary> /// <summary>

View File

@@ -125,9 +125,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
/// 获取或设置付款时间。 /// 获取或设置付款时间。
/// </summary> /// </summary>
[Newtonsoft.Json.JsonProperty("pay_time")] [Newtonsoft.Json.JsonProperty("pay_time")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339NullableDateTimeOffsetConverter))] [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.Rfc3339DateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("pay_time")] [System.Text.Json.Serialization.JsonPropertyName("pay_time")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339NullableDateTimeOffsetConverter))] [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.Rfc3339DateTimeOffsetConverter))]
public DateTimeOffset? SucceedTime { get; set; } public DateTimeOffset? SucceedTime { get; set; }
} }
} }

View File

@@ -22,9 +22,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
/// 获取或设置链接过期时间。 /// 获取或设置链接过期时间。
/// </summary> /// </summary>
[Newtonsoft.Json.JsonProperty("expire_time")] [Newtonsoft.Json.JsonProperty("expire_time")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339DateTimeOffsetConverter))] [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.Rfc3339DateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("expire_time")] [System.Text.Json.Serialization.JsonPropertyName("expire_time")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339DateTimeOffsetConverter))] [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.Rfc3339DateTimeOffsetConverter))]
public DateTimeOffset ExpireTime { get; set; } public DateTimeOffset ExpireTime { get; set; }
} }
} }

View File

@@ -64,9 +64,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
/// 获取或设置分账成功时间。 /// 获取或设置分账成功时间。
/// </summary> /// </summary>
[Newtonsoft.Json.JsonProperty("succeeded_time")] [Newtonsoft.Json.JsonProperty("succeeded_time")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339NullableDateTimeOffsetConverter))] [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.Rfc3339DateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("succeeded_time")] [System.Text.Json.Serialization.JsonPropertyName("succeeded_time")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339NullableDateTimeOffsetConverter))] [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.Rfc3339DateTimeOffsetConverter))]
public DateTimeOffset? SucceedTime { get; set; } public DateTimeOffset? SucceedTime { get; set; }
/// <summary> /// <summary>
@@ -117,9 +117,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
/// 获取或设置分账完成时间。 /// 获取或设置分账完成时间。
/// </summary> /// </summary>
[Newtonsoft.Json.JsonProperty("finished_time")] [Newtonsoft.Json.JsonProperty("finished_time")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339NullableDateTimeOffsetConverter))] [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.Rfc3339DateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("finished_time")] [System.Text.Json.Serialization.JsonPropertyName("finished_time")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339NullableDateTimeOffsetConverter))] [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.Rfc3339DateTimeOffsetConverter))]
public DateTimeOffset? FinishTime { get; set; } public DateTimeOffset? FinishTime { get; set; }
/// <summary> /// <summary>

View File

@@ -67,18 +67,18 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
/// 获取或设置退款成功时间。 /// 获取或设置退款成功时间。
/// </summary> /// </summary>
[Newtonsoft.Json.JsonProperty("succeeded_time")] [Newtonsoft.Json.JsonProperty("succeeded_time")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339NullableDateTimeOffsetConverter))] [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.Rfc3339DateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("succeeded_time")] [System.Text.Json.Serialization.JsonPropertyName("succeeded_time")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339NullableDateTimeOffsetConverter))] [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.Rfc3339DateTimeOffsetConverter))]
public DateTimeOffset? SucceedTime { get; set; } public DateTimeOffset? SucceedTime { get; set; }
/// <summary> /// <summary>
/// 获取或设置退款创建时间。 /// 获取或设置退款创建时间。
/// </summary> /// </summary>
[Newtonsoft.Json.JsonProperty("created_time")] [Newtonsoft.Json.JsonProperty("created_time")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339DateTimeOffsetConverter))] [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.Rfc3339DateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("created_time")] [System.Text.Json.Serialization.JsonPropertyName("created_time")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339DateTimeOffsetConverter))] [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.Rfc3339DateTimeOffsetConverter))]
public DateTimeOffset CreateTime { get; set; } public DateTimeOffset CreateTime { get; set; }
} }
} }

View File

@@ -46,9 +46,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
/// 获取或设置分账成功时间。 /// 获取或设置分账成功时间。
/// </summary> /// </summary>
[Newtonsoft.Json.JsonProperty("succeeded_time")] [Newtonsoft.Json.JsonProperty("succeeded_time")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339NullableDateTimeOffsetConverter))] [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.Rfc3339DateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("succeeded_time")] [System.Text.Json.Serialization.JsonPropertyName("succeeded_time")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339NullableDateTimeOffsetConverter))] [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.Rfc3339DateTimeOffsetConverter))]
public DateTimeOffset? SucceedTime { get; set; } public DateTimeOffset? SucceedTime { get; set; }
/// <summary> /// <summary>

View File

@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net461; net47; netstandard2.0; net6.0</TargetFrameworks> <TargetFrameworks>net461; net471; netstandard2.0; net6.0</TargetFrameworks>
<LangVersion>8.0</LangVersion> <LangVersion>10.0</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<NullableReferenceTypes>true</NullableReferenceTypes> <NullableReferenceTypes>true</NullableReferenceTypes>
</PropertyGroup> </PropertyGroup>
@@ -14,7 +14,7 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat</PackageProjectUrl> <PackageProjectUrl>https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat</PackageProjectUrl>
<PackageTags>Flurl.Http Tencent Tenpay FiT 腾讯金融科技服务平台 腾讯金融科技 腾讯金融 腾讯商企付 腾讯微企付 商企付 微企付</PackageTags> <PackageTags>Flurl.Http Tencent Tenpay FiT 腾讯金融科技服务平台 腾讯金融科技 腾讯金融 腾讯商企付 腾讯微企付 商企付 微企付</PackageTags>
<Version>2.2.0</Version> <Version>3.0.0-preview.1</Version>
<Description>基于 Flurl.Http 的微企付 API 客户端。</Description> <Description>基于 Flurl.Http 的微企付 API 客户端。</Description>
<Authors>Fu Diwei</Authors> <Authors>Fu Diwei</Authors>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
@@ -36,13 +36,13 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="System.Web" Condition="'$(TargetFramework)' == 'net461' Or '$(TargetFramework)' == 'net47'" /> <Reference Include="System.Web" Condition="'$(TargetFramework)' == 'net461' Or '$(TargetFramework)' == 'net471'" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BouncyCastle.Cryptography" Version="2.2.1" /> <PackageReference Include="BouncyCastle.Cryptography" Version="2.2.1" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" Condition="'$(TargetFramework)' == 'net461'" /> <PackageReference Include="System.ValueTuple" Version="4.5.0" Condition="'$(TargetFramework)' == 'net461'" />
<PackageReference Include="SKIT.FlurlHttpClient.Common" Version="2.6.0" /> <PackageReference Include="SKIT.FlurlHttpClient.Common" Version="3.0.0-preview.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -61,7 +61,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Settings
internal Credentials(WechatTenpayBusinessClientOptions options) internal Credentials(WechatTenpayBusinessClientOptions options)
{ {
if (options == null) throw new ArgumentNullException(nameof(options)); if (options is null) throw new ArgumentNullException(nameof(options));
PlatformId = options.PlatformId; PlatformId = options.PlatformId;
PlatformCertificateSerialNumber = options.PlatformCertificateSerialNumber; PlatformCertificateSerialNumber = options.PlatformCertificateSerialNumber;

View File

@@ -72,8 +72,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
/// <returns>签名字节数组。</returns> /// <returns>签名字节数组。</returns>
public static byte[] SignWithSHA256(byte[] privateKeyBytes, byte[] msgBytes) public static byte[] SignWithSHA256(byte[] privateKeyBytes, byte[] msgBytes)
{ {
if (privateKeyBytes == null) throw new ArgumentNullException(nameof(privateKeyBytes)); if (privateKeyBytes is null) throw new ArgumentNullException(nameof(privateKeyBytes));
if (msgBytes == null) throw new ArgumentNullException(nameof(msgBytes)); if (msgBytes is null) throw new ArgumentNullException(nameof(msgBytes));
RsaKeyParameters rsaPrivateKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes); RsaKeyParameters rsaPrivateKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes);
return SignWithSHA256(rsaPrivateKeyParams, msgBytes); return SignWithSHA256(rsaPrivateKeyParams, msgBytes);
@@ -87,8 +87,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
/// <returns>经 Base64 编码的签名。</returns> /// <returns>经 Base64 编码的签名。</returns>
public static string SignWithSHA256(string privateKey, string message) public static string SignWithSHA256(string privateKey, string message)
{ {
if (privateKey == null) throw new ArgumentNullException(nameof(privateKey)); if (privateKey is null) throw new ArgumentNullException(nameof(privateKey));
if (message == null) throw new ArgumentNullException(nameof(message)); if (message is null) throw new ArgumentNullException(nameof(message));
byte[] privateKeyBytes = ConvertPrivateKeyPkcs8PemToByteArray(privateKey); byte[] privateKeyBytes = ConvertPrivateKeyPkcs8PemToByteArray(privateKey);
byte[] msgBytes = Encoding.UTF8.GetBytes(message); byte[] msgBytes = Encoding.UTF8.GetBytes(message);
@@ -105,9 +105,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
/// <returns>验证结果。</returns> /// <returns>验证结果。</returns>
public static bool VerifyWithSHA256(byte[] publicKeyBytes, byte[] msgBytes, byte[] signBytes) public static bool VerifyWithSHA256(byte[] publicKeyBytes, byte[] msgBytes, byte[] signBytes)
{ {
if (publicKeyBytes == null) throw new ArgumentNullException(nameof(publicKeyBytes)); if (publicKeyBytes is null) throw new ArgumentNullException(nameof(publicKeyBytes));
if (msgBytes == null) throw new ArgumentNullException(nameof(msgBytes)); if (msgBytes is null) throw new ArgumentNullException(nameof(msgBytes));
if (signBytes == null) throw new ArgumentNullException(nameof(signBytes)); if (signBytes is null) throw new ArgumentNullException(nameof(signBytes));
RsaKeyParameters rsaPublicKeyParams = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes); RsaKeyParameters rsaPublicKeyParams = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes);
return VerifyWithSHA256(rsaPublicKeyParams, msgBytes, signBytes); return VerifyWithSHA256(rsaPublicKeyParams, msgBytes, signBytes);
@@ -122,9 +122,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
/// <returns>验证结果。</returns> /// <returns>验证结果。</returns>
public static bool VerifyWithSHA256(string publicKey, string message, string signature) public static bool VerifyWithSHA256(string publicKey, string message, string signature)
{ {
if (publicKey == null) throw new ArgumentNullException(nameof(publicKey)); if (publicKey is null) throw new ArgumentNullException(nameof(publicKey));
if (message == null) throw new ArgumentNullException(nameof(message)); if (message is null) throw new ArgumentNullException(nameof(message));
if (signature == null) throw new ArgumentNullException(nameof(signature)); if (signature is null) throw new ArgumentNullException(nameof(signature));
byte[] publicKeyBytes = ConvertPublicKeyPkcs8PemToByteArray(publicKey); byte[] publicKeyBytes = ConvertPublicKeyPkcs8PemToByteArray(publicKey);
byte[] msgBytes = Encoding.UTF8.GetBytes(message); byte[] msgBytes = Encoding.UTF8.GetBytes(message);
@@ -141,8 +141,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
/// <returns>解密后的数据字节数组。</returns> /// <returns>解密后的数据字节数组。</returns>
public static byte[] DecryptWithECB(byte[] privateKeyBytes, byte[] cipherBytes, string paddingAlgorithm = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1) public static byte[] DecryptWithECB(byte[] privateKeyBytes, byte[] cipherBytes, string paddingAlgorithm = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1)
{ {
if (privateKeyBytes == null) throw new ArgumentNullException(nameof(privateKeyBytes)); if (privateKeyBytes is null) throw new ArgumentNullException(nameof(privateKeyBytes));
if (cipherBytes == null) throw new ArgumentNullException(nameof(cipherBytes)); if (cipherBytes is null) throw new ArgumentNullException(nameof(cipherBytes));
RsaKeyParameters rsaPrivateKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes); RsaKeyParameters rsaPrivateKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes);
return DecryptWithECB(rsaPrivateKeyParams, cipherBytes, paddingAlgorithm); return DecryptWithECB(rsaPrivateKeyParams, cipherBytes, paddingAlgorithm);
@@ -157,8 +157,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
/// <returns>解密后的文本数据。</returns> /// <returns>解密后的文本数据。</returns>
public static string DecryptWithECB(string privateKey, string cipherText, string paddingAlgorithm = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1) public static string DecryptWithECB(string privateKey, string cipherText, string paddingAlgorithm = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1)
{ {
if (privateKey == null) throw new ArgumentNullException(nameof(privateKey)); if (privateKey is null) throw new ArgumentNullException(nameof(privateKey));
if (cipherText == null) throw new ArgumentNullException(nameof(cipherText)); if (cipherText is null) throw new ArgumentNullException(nameof(cipherText));
byte[] privateKeyBytes = ConvertPrivateKeyPkcs8PemToByteArray(privateKey); byte[] privateKeyBytes = ConvertPrivateKeyPkcs8PemToByteArray(privateKey);
byte[] cipherBytes = Convert.FromBase64String(cipherText); byte[] cipherBytes = Convert.FromBase64String(cipherText);
@@ -175,8 +175,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
/// <returns>加密后的数据字节数组。</returns> /// <returns>加密后的数据字节数组。</returns>
public static byte[] EncryptWithECB(byte[] publicKeyBytes, byte[] plainBytes, string paddingAlgorithm = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1) public static byte[] EncryptWithECB(byte[] publicKeyBytes, byte[] plainBytes, string paddingAlgorithm = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1)
{ {
if (publicKeyBytes == null) throw new ArgumentNullException(nameof(publicKeyBytes)); if (publicKeyBytes is null) throw new ArgumentNullException(nameof(publicKeyBytes));
if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes)); if (plainBytes is null) throw new ArgumentNullException(nameof(plainBytes));
RsaKeyParameters rsaPublicKeyParams = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes); RsaKeyParameters rsaPublicKeyParams = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes);
return EncryptWithECB(rsaPublicKeyParams, plainBytes, paddingAlgorithm); return EncryptWithECB(rsaPublicKeyParams, plainBytes, paddingAlgorithm);
@@ -191,8 +191,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
/// <returns>经 Base64 编码的加密数据。</returns> /// <returns>经 Base64 编码的加密数据。</returns>
public static string EncryptWithECB(string publicKey, string plainText, string paddingAlgorithm = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1) public static string EncryptWithECB(string publicKey, string plainText, string paddingAlgorithm = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1)
{ {
if (publicKey == null) throw new ArgumentNullException(nameof(publicKey)); if (publicKey is null) throw new ArgumentNullException(nameof(publicKey));
if (plainText == null) throw new ArgumentNullException(nameof(plainText)); if (plainText is null) throw new ArgumentNullException(nameof(plainText));
byte[] publicKeyBytes = ConvertPublicKeyPkcs8PemToByteArray(publicKey); byte[] publicKeyBytes = ConvertPublicKeyPkcs8PemToByteArray(publicKey);
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText); byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);

View File

@@ -16,7 +16,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
/// <returns>信息摘要字节数组。</returns> /// <returns>信息摘要字节数组。</returns>
public static byte[] Hash(byte[] bytes) public static byte[] Hash(byte[] bytes)
{ {
if (bytes == null) throw new ArgumentNullException(nameof(bytes)); if (bytes is null) throw new ArgumentNullException(nameof(bytes));
using SHA256 sha = SHA256.Create(); using SHA256 sha = SHA256.Create();
return sha.ComputeHash(bytes); return sha.ComputeHash(bytes);
@@ -29,7 +29,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
/// <returns>信息摘要。</returns> /// <returns>信息摘要。</returns>
public static string Hash(string message) public static string Hash(string message)
{ {
if (message == null) throw new ArgumentNullException(nameof(message)); if (message is null) throw new ArgumentNullException(nameof(message));
byte[] msgBytes = Encoding.UTF8.GetBytes(message); byte[] msgBytes = Encoding.UTF8.GetBytes(message);
byte[] hashBytes = Hash(msgBytes); byte[] hashBytes = Hash(msgBytes);

View File

@@ -16,7 +16,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
/// <returns>哈希字节数组。</returns> /// <returns>哈希字节数组。</returns>
public static byte[] Hash(byte[] bytes) public static byte[] Hash(byte[] bytes)
{ {
if (bytes == null) throw new ArgumentNullException(nameof(bytes)); if (bytes is null) throw new ArgumentNullException(nameof(bytes));
SM3Digest sm3 = new SM3Digest(); SM3Digest sm3 = new SM3Digest();
sm3.BlockUpdate(bytes, 0, bytes.Length); sm3.BlockUpdate(bytes, 0, bytes.Length);
@@ -32,7 +32,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
/// <returns>哈希值。</returns> /// <returns>哈希值。</returns>
public static string Hash(string message) public static string Hash(string message)
{ {
if (message == null) throw new ArgumentNullException(nameof(message)); if (message is null) throw new ArgumentNullException(nameof(message));
byte[] msgBytes = Encoding.UTF8.GetBytes(message); byte[] msgBytes = Encoding.UTF8.GetBytes(message);
byte[] hashBytes = Hash(msgBytes); byte[] hashBytes = Hash(msgBytes);

View File

@@ -39,8 +39,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
/// <returns>加密后的数据字节数组。</returns> /// <returns>加密后的数据字节数组。</returns>
public static byte[] EncryptWithCBC(byte[] keyBytes, byte[] ivBytes, byte[] plainBytes, string paddingMode = SM4_CIPHER_PADDING_PKCS7PADDING) public static byte[] EncryptWithCBC(byte[] keyBytes, byte[] ivBytes, byte[] plainBytes, string paddingMode = SM4_CIPHER_PADDING_PKCS7PADDING)
{ {
if (keyBytes == null) throw new ArgumentNullException(nameof(keyBytes)); if (keyBytes is null) throw new ArgumentNullException(nameof(keyBytes));
if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes)); if (plainBytes is null) throw new ArgumentNullException(nameof(plainBytes));
KeyParameter sm4KeyParams = ParameterUtilities.CreateKeyParameter(SM4_ALGORITHM_NAME, keyBytes); KeyParameter sm4KeyParams = ParameterUtilities.CreateKeyParameter(SM4_ALGORITHM_NAME, keyBytes);
ParametersWithIV sm4keyParamsWithIv = new ParametersWithIV(sm4KeyParams, ivBytes); ParametersWithIV sm4keyParamsWithIv = new ParametersWithIV(sm4KeyParams, ivBytes);
@@ -57,8 +57,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
/// <returns>经 Base64 编码的加密数据。</returns> /// <returns>经 Base64 编码的加密数据。</returns>
public static string EncryptWithCBC(string key, string iv, string plainText, string paddingMode = SM4_CIPHER_PADDING_PKCS7PADDING) public static string EncryptWithCBC(string key, string iv, string plainText, string paddingMode = SM4_CIPHER_PADDING_PKCS7PADDING)
{ {
if (key == null) throw new ArgumentNullException(nameof(key)); if (key is null) throw new ArgumentNullException(nameof(key));
if (plainText == null) throw new ArgumentNullException(nameof(plainText)); if (plainText is null) throw new ArgumentNullException(nameof(plainText));
byte[] keyBytes = Convert.FromBase64String(key); byte[] keyBytes = Convert.FromBase64String(key);
byte[] ivBytes = Convert.FromBase64String(iv); byte[] ivBytes = Convert.FromBase64String(iv);
@@ -77,8 +77,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
/// <returns>解密后的数据字节数组。</returns> /// <returns>解密后的数据字节数组。</returns>
public static byte[] DecryptWithCBC(byte[] keyBytes, byte[] ivBytes, byte[] cipherBytes, string paddingMode = SM4_CIPHER_PADDING_PKCS7PADDING) public static byte[] DecryptWithCBC(byte[] keyBytes, byte[] ivBytes, byte[] cipherBytes, string paddingMode = SM4_CIPHER_PADDING_PKCS7PADDING)
{ {
if (keyBytes == null) throw new ArgumentNullException(nameof(keyBytes)); if (keyBytes is null) throw new ArgumentNullException(nameof(keyBytes));
if (cipherBytes == null) throw new ArgumentNullException(nameof(cipherBytes)); if (cipherBytes is null) throw new ArgumentNullException(nameof(cipherBytes));
KeyParameter sm4KeyParams = ParameterUtilities.CreateKeyParameter(SM4_ALGORITHM_NAME, keyBytes); KeyParameter sm4KeyParams = ParameterUtilities.CreateKeyParameter(SM4_ALGORITHM_NAME, keyBytes);
ParametersWithIV sm4keyParamsWithIv = new ParametersWithIV(sm4KeyParams, ivBytes); ParametersWithIV sm4keyParamsWithIv = new ParametersWithIV(sm4KeyParams, ivBytes);
@@ -95,8 +95,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
/// <returns>解密后的文本数据。</returns> /// <returns>解密后的文本数据。</returns>
public static string DecryptWithCBC(string key, string iv, string cipherText, string paddingMode = SM4_CIPHER_PADDING_PKCS7PADDING) public static string DecryptWithCBC(string key, string iv, string cipherText, string paddingMode = SM4_CIPHER_PADDING_PKCS7PADDING)
{ {
if (key == null) throw new ArgumentNullException(nameof(key)); if (key is null) throw new ArgumentNullException(nameof(key));
if (cipherText == null) throw new ArgumentNullException(nameof(cipherText)); if (cipherText is null) throw new ArgumentNullException(nameof(cipherText));
byte[] keyBytes = Convert.FromBase64String(key); byte[] keyBytes = Convert.FromBase64String(key);
byte[] ivBytes = Convert.FromBase64String(iv); byte[] ivBytes = Convert.FromBase64String(iv);

View File

@@ -15,10 +15,10 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
public static MultipartFormDataContent Build(string fileName, byte[] fileBytes, string fileContentType, string fileMetaJson, string formDataName, Action<HttpContent> configureMetaHttpContent, Action<HttpContent> configureFileHttpContent) public static MultipartFormDataContent Build(string fileName, byte[] fileBytes, string fileContentType, string fileMetaJson, string formDataName, Action<HttpContent> configureMetaHttpContent, Action<HttpContent> configureFileHttpContent)
{ {
if (fileName == null) throw new ArgumentNullException(nameof(fileName)); if (fileName is null) throw new ArgumentNullException(nameof(fileName));
if (fileMetaJson == null) throw new ArgumentNullException(nameof(fileMetaJson)); if (fileMetaJson is null) throw new ArgumentNullException(nameof(fileMetaJson));
if (formDataName == null) throw new ArgumentNullException(nameof(formDataName)); if (formDataName is null) throw new ArgumentNullException(nameof(formDataName));
if (configureFileHttpContent == null) throw new ArgumentNullException(nameof(configureFileHttpContent)); if (configureFileHttpContent is null) throw new ArgumentNullException(nameof(configureFileHttpContent));
fileBytes = fileBytes ?? Array.Empty<byte>(); fileBytes = fileBytes ?? Array.Empty<byte>();
fileContentType = string.IsNullOrEmpty(fileContentType) ? "application/octet-stream" : fileContentType; fileContentType = string.IsNullOrEmpty(fileContentType) ? "application/octet-stream" : fileContentType;

View File

@@ -15,8 +15,8 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
private static void InnerReplacePropertyStringValue<T>(ref T obj, ReplacePropertyStringValueReplacementHandler replacement) private static void InnerReplacePropertyStringValue<T>(ref T obj, ReplacePropertyStringValueReplacementHandler replacement)
{ {
if (obj == null) throw new ArgumentNullException(nameof(obj)); if (obj is null) throw new ArgumentNullException(nameof(obj));
if (replacement == null) throw new ArgumentNullException(nameof(replacement)); if (replacement is null) throw new ArgumentNullException(nameof(replacement));
Type objType = obj.GetType(); Type objType = obj.GetType();
if (!objType.IsClass) if (!objType.IsClass)
@@ -82,7 +82,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
Type elementType = element.GetType(); Type elementType = element.GetType();
if (elementType == typeof(string)) if (elementType == typeof(string))
{ {
if (currentProp == null) if (currentProp is null)
continue; continue;
if (!currentProp.CanWrite) if (!currentProp.CanWrite)
continue; continue;
@@ -121,7 +121,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
Type elementType = element.GetType(); Type elementType = element.GetType();
if (elementType == typeof(string)) if (elementType == typeof(string))
{ {
if (currentProp == null) if (currentProp is null)
continue; continue;
if (!currentProp.CanWrite) if (!currentProp.CanWrite)
continue; continue;
@@ -160,7 +160,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
Type entryValueType = entryValue.GetType(); Type entryValueType = entryValue.GetType();
if (entryValueType == typeof(string)) if (entryValueType == typeof(string))
{ {
if (currentProp == null) if (currentProp is null)
continue; continue;
if (!currentProp.CanWrite) if (!currentProp.CanWrite)
continue; continue;

View File

@@ -35,20 +35,32 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
/// </summary> /// </summary>
/// <param name="options">配置项。</param> /// <param name="options">配置项。</param>
public WechatTenpayBusinessClient(WechatTenpayBusinessClientOptions options) public WechatTenpayBusinessClient(WechatTenpayBusinessClientOptions options)
: this(options, null)
{ {
if (options == null) throw new ArgumentNullException(nameof(options)); }
/// <summary>
///
/// </summary>
/// <param name="options"></param>
/// <param name="httpClient"></param>
/// <param name="disposeClient"></param>
internal protected WechatTenpayBusinessClient(WechatTenpayBusinessClientOptions options, HttpClient? httpClient, bool disposeClient = true)
: base(httpClient, disposeClient)
{
if (options is null) throw new ArgumentNullException(nameof(options));
Credentials = new Settings.Credentials(options); Credentials = new Settings.Credentials(options);
AutoEncryptRequestSensitiveProperty = options.AutoEncryptRequestSensitiveProperty; AutoEncryptRequestSensitiveProperty = options.AutoEncryptRequestSensitiveProperty;
AutoDecryptResponseSensitiveProperty = options.AutoDecryptResponseSensitiveProperty; AutoDecryptResponseSensitiveProperty = options.AutoDecryptResponseSensitiveProperty;
FlurlClient.BaseUrl = options.Endpoint ?? WechatTenpayBusinessEndpoints.DEFAULT; FlurlClient.BaseUrl = options.Endpoint ?? WechatTenpayBusinessEndpoints.DEFAULT;
FlurlClient.Headers.Remove(FlurlHttpClient.Constants.HttpHeaders.Accept); FlurlClient.Headers.Remove(HttpHeaders.Accept);
FlurlClient.Headers.Remove(FlurlHttpClient.Constants.HttpHeaders.AcceptLanguage); FlurlClient.Headers.Remove(HttpHeaders.AcceptLanguage);
FlurlClient.WithHeader(FlurlHttpClient.Constants.HttpHeaders.Accept, "application/json"); FlurlClient.WithHeader(HttpHeaders.Accept, "application/json");
FlurlClient.WithTimeout(TimeSpan.FromMilliseconds(options.Timeout)); FlurlClient.WithTimeout(options.Timeout <= 0 ? Timeout.InfiniteTimeSpan : TimeSpan.FromMilliseconds(options.Timeout));
Interceptors.Add(new Interceptors.WechatTenpayBusinessRequestSignatureInterceptor( Interceptors.Add(new Interceptors.WechatTenpayBusinessRequestSigningInterceptor(
signAlg: options.SignAlgorithm, signAlg: options.SignAlgorithm,
platformId: options.PlatformId, platformId: options.PlatformId,
platformCertSn: options.PlatformCertificateSerialNumber, platformCertSn: options.PlatformCertificateSerialNumber,
@@ -63,27 +75,22 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
/// 使用当前客户端生成一个新的 <see cref="IFlurlRequest"/> 对象。 /// 使用当前客户端生成一个新的 <see cref="IFlurlRequest"/> 对象。
/// </summary> /// </summary>
/// <param name="request"></param> /// <param name="request"></param>
/// <param name="method"></param> /// <param name="httpMethod"></param>
/// <param name="urlSegments"></param> /// <param name="urlSegments"></param>
/// <returns></returns> /// <returns></returns>
public IFlurlRequest CreateRequest(WechatTenpayBusinessRequest request, HttpMethod method, params object[] urlSegments) public IFlurlRequest CreateFlurlRequest(WechatTenpayBusinessRequest request, HttpMethod httpMethod, params object[] urlSegments)
{ {
IFlurlRequest flurlRequest = FlurlClient.Request(urlSegments).WithVerb(method); IFlurlRequest flurlRequest = base.CreateFlurlRequest(request, httpMethod, urlSegments);
if (AutoEncryptRequestSensitiveProperty) if (AutoEncryptRequestSensitiveProperty)
{ {
this.EncryptRequestSensitiveProperty(request); this.EncryptRequestSensitiveProperty(request);
} }
if (request.Timeout != null) if (request.WechatpayEncryption is not null)
{
flurlRequest.WithTimeout(TimeSpan.FromMilliseconds(request.Timeout.Value));
}
if (request.Encryption != null)
{ {
flurlRequest.Headers.Remove("TBEP-Encrypt"); flurlRequest.Headers.Remove("TBEP-Encrypt");
flurlRequest.WithHeader("TBEP-Encrypt", $"enc_key=\"{request.Encryption.EncryptedKey}\",iv=\"{Convert.ToBase64String(Encoding.UTF8.GetBytes(request.Encryption.IV))}\",tbep_serial_number=\"{request.Encryption.SerialNumber}\",algorithm=\"{request.Encryption.Algorithm}\""); flurlRequest.WithHeader("TBEP-Encrypt", $"enc_key=\"{request.WechatpayEncryption.EncryptedKey}\",iv=\"{Convert.ToBase64String(Encoding.UTF8.GetBytes(request.WechatpayEncryption.IV))}\",tbep_serial_number=\"{request.WechatpayEncryption.SerialNumber}\",algorithm=\"{request.WechatpayEncryption.Algorithm}\"");
} }
return flurlRequest; return flurlRequest;
@@ -97,30 +104,19 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
/// <param name="httpContent"></param> /// <param name="httpContent"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
public async Task<T> SendRequestAsync<T>(IFlurlRequest flurlRequest, HttpContent? httpContent = null, CancellationToken cancellationToken = default) public async Task<T> SendFlurlRequestAsync<T>(IFlurlRequest flurlRequest, HttpContent? httpContent = null, CancellationToken cancellationToken = default)
where T : WechatTenpayBusinessResponse, new() where T : WechatTenpayBusinessResponse, new()
{ {
if (flurlRequest == null) throw new ArgumentNullException(nameof(flurlRequest)); if (flurlRequest is null) throw new ArgumentNullException(nameof(flurlRequest));
if (httpContent != null) if (httpContent is not null)
{ {
if (string.IsNullOrEmpty(httpContent.Headers.ContentType?.MediaType)) if (string.IsNullOrEmpty(httpContent.Headers.ContentType?.MediaType))
httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
} }
try using IFlurlResponse flurlResponse = await base.SendFlurlRequestAsync(flurlRequest, httpContent, cancellationToken);
{ return await WrapFlurlResponseAsJsonAsync<T>(flurlResponse, cancellationToken);
using IFlurlResponse flurlResponse = await base.SendRequestAsync(flurlRequest, httpContent, cancellationToken);
return await WrapResponseWithJsonAsync<T>(flurlResponse, cancellationToken);
}
catch (FlurlHttpTimeoutException ex)
{
throw new Exceptions.WechatTenpayBusinessRequestTimeoutException(ex.Message, ex);
}
catch (FlurlHttpException ex)
{
throw new WechatTenpayBusinessException(ex.Message, ex);
}
} }
/// <summary> /// <summary>
@@ -131,36 +127,25 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
/// <param name="data"></param> /// <param name="data"></param>
/// <param name="cancellationToken"></param> /// <param name="cancellationToken"></param>
/// <returns></returns> /// <returns></returns>
public async Task<T> SendRequestWithJsonAsync<T>(IFlurlRequest flurlRequest, object? data = null, CancellationToken cancellationToken = default) public async Task<T> SendFlurlRequestAsJsonAsync<T>(IFlurlRequest flurlRequest, object? data = null, CancellationToken cancellationToken = default)
where T : WechatTenpayBusinessResponse, new() where T : WechatTenpayBusinessResponse, new()
{ {
if (flurlRequest == null) throw new ArgumentNullException(nameof(flurlRequest)); if (flurlRequest is null) throw new ArgumentNullException(nameof(flurlRequest));
try bool isSimpleRequest = data is null ||
{
bool isSimpleRequest = data == null ||
flurlRequest.Verb == HttpMethod.Get || flurlRequest.Verb == HttpMethod.Get ||
flurlRequest.Verb == HttpMethod.Head || flurlRequest.Verb == HttpMethod.Head ||
flurlRequest.Verb == HttpMethod.Options; flurlRequest.Verb == HttpMethod.Options;
using IFlurlResponse flurlResponse = isSimpleRequest ? using IFlurlResponse flurlResponse = isSimpleRequest ?
await base.SendRequestAsync(flurlRequest, null, cancellationToken) : await base.SendFlurlRequestAsync(flurlRequest, null, cancellationToken) :
await base.SendRequestWithJsonAsync(flurlRequest, data, cancellationToken); await base.SendFlurlRequestAsJsonAsync(flurlRequest, data, cancellationToken);
return await WrapResponseWithJsonAsync<T>(flurlResponse, cancellationToken); return await WrapFlurlResponseAsJsonAsync<T>(flurlResponse, cancellationToken);
}
catch (FlurlHttpTimeoutException ex)
{
throw new Exceptions.WechatTenpayBusinessRequestTimeoutException(ex.Message, ex);
}
catch (FlurlHttpException ex)
{
throw new WechatTenpayBusinessException(ex.Message, ex);
}
} }
private new async Task<TResponse> WrapResponseWithJsonAsync<TResponse>(IFlurlResponse flurlResponse, CancellationToken cancellationToken = default) private new async Task<TResponse> WrapFlurlResponseAsJsonAsync<TResponse>(IFlurlResponse flurlResponse, CancellationToken cancellationToken = default)
where TResponse : WechatTenpayBusinessResponse, new() where TResponse : WechatTenpayBusinessResponse, new()
{ {
TResponse result = await base.WrapResponseWithJsonAsync<TResponse>(flurlResponse, cancellationToken); TResponse result = await base.WrapFlurlResponseAsJsonAsync<TResponse>(flurlResponse, cancellationToken);
string? strEncryption = flurlResponse.Headers.FirstOrDefault("TBEP-Encrypt"); string? strEncryption = flurlResponse.Headers.FirstOrDefault("TBEP-Encrypt");
if (!string.IsNullOrEmpty(strEncryption)) if (!string.IsNullOrEmpty(strEncryption))
@@ -172,13 +157,13 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
k => k[0], k => k[0],
v => v.Length > 1 ? v[1].TrimStart('\"').TrimEnd('\"') : null v => v.Length > 1 ? v[1].TrimStart('\"').TrimEnd('\"') : null
); );
result.Encryption = new WechatTenpayBusinessResponseEncryption(); result.WechatpayEncryption = new WechatTenpayBusinessResponseEncryption();
result.Encryption.PlatformId = dictEncryption.GetValueOrDefault("platform_id"); result.WechatpayEncryption.PlatformId = dictEncryption.GetValueOrDefault("platform_id");
result.Encryption.EnterpriseId = dictEncryption.GetValueOrDefault("ent_id"); result.WechatpayEncryption.EnterpriseId = dictEncryption.GetValueOrDefault("ent_id");
result.Encryption.EncryptedKey = dictEncryption.GetValueOrDefault("enc_key"); result.WechatpayEncryption.EncryptedKey = dictEncryption.GetValueOrDefault("enc_key");
result.Encryption.IV = dictEncryption.GetValueOrDefault("iv"); result.WechatpayEncryption.IV = dictEncryption.GetValueOrDefault("iv");
result.Encryption.SerialNumber = dictEncryption.GetValueOrDefault("platform_serial_number") ?? dictEncryption.GetValueOrDefault("enterprise_serial_number"); result.WechatpayEncryption.SerialNumber = dictEncryption.GetValueOrDefault("platform_serial_number") ?? dictEncryption.GetValueOrDefault("enterprise_serial_number");
result.Encryption.Algorithm = dictEncryption.GetValueOrDefault("algorithm"); result.WechatpayEncryption.Algorithm = dictEncryption.GetValueOrDefault("algorithm");
if (AutoDecryptResponseSensitiveProperty && result.IsSuccessful()) if (AutoDecryptResponseSensitiveProperty && result.IsSuccessful())
{ {

View File

@@ -7,19 +7,25 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
{ {
/// <summary> /// <summary>
/// 获取或设置请求超时时间(单位:毫秒)。 /// 获取或设置请求超时时间(单位:毫秒)。
/// <para>默认值30000</para> /// <para>
/// 默认值30000
/// </para>
/// </summary> /// </summary>
public int Timeout { get; set; } = 30 * 1000; public int Timeout { get; set; } = 30 * 1000;
/// <summary> /// <summary>
/// 获取或设置微企付 API 入口点。 /// 获取或设置微企付 API 入口点。
/// <para>默认值:<see cref="WechatTenpayBusinessEndpoints.DEFAULT"/></para> /// <para>
/// 默认值:<see cref="WechatTenpayBusinessEndpoints.DEFAULT"/>
/// </para>
/// </summary> /// </summary>
public string Endpoint { get; set; } = WechatTenpayBusinessEndpoints.DEFAULT; public string Endpoint { get; set; } = WechatTenpayBusinessEndpoints.DEFAULT;
/// <summary> /// <summary>
/// 获取或设置微企付 API 签名认证方式。 /// 获取或设置微企付 API 签名认证方式。
/// <para>默认值:<see cref="Constants.SignAlgorithms.SHA245_WITH_RSA"/></para> /// <para>
/// 默认值:<see cref="Constants.SignAlgorithms.SHA245_WITH_RSA"/>
/// </para>
/// </summary> /// </summary>
public string SignAlgorithm { get; set; } = Constants.SignAlgorithms.SHA245_WITH_RSA; public string SignAlgorithm { get; set; } = Constants.SignAlgorithms.SHA245_WITH_RSA;
@@ -63,15 +69,11 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
/// </summary> /// </summary>
public string TBEPCertificatePublicKey { get; set; } = default!; public string TBEPCertificatePublicKey { get; set; } = default!;
/// <summary>
/// 获取或设置是否自动加密请求中的敏感字段数据。
/// <para>注意:启用该功能需配合 <see cref="SensitivePropertyEncryptionSM4Key"/> 和 <see cref="SensitivePropertyEncryptionSM4IV"/> 使用。</para>
/// </summary>
public bool AutoEncryptRequestSensitiveProperty { get; set; }
/// <summary> /// <summary>
/// 获取或设置自动加密请求重敏感字段数据时使用的算法。 /// 获取或设置自动加密请求重敏感字段数据时使用的算法。
/// <para>默认值:<see cref="Constants.EncryptionAlgorithms.RSA_OAEP_WITH_SM4_128_CBC"/></para> /// <para>
/// 默认值:<see cref="Constants.EncryptionAlgorithms.RSA_OAEP_WITH_SM4_128_CBC"/>
/// </para>
/// </summary> /// </summary>
public string SensitivePropertyEncryptionAlgorithm { get; set; } = Constants.EncryptionAlgorithms.RSA_OAEP_WITH_SM4_128_CBC; public string SensitivePropertyEncryptionAlgorithm { get; set; } = Constants.EncryptionAlgorithms.RSA_OAEP_WITH_SM4_128_CBC;
@@ -85,6 +87,14 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
/// </summary> /// </summary>
public string? SensitivePropertyEncryptionSM4IV { get; set; } public string? SensitivePropertyEncryptionSM4IV { get; set; }
/// <summary>
/// 获取或设置是否自动加密请求中的敏感字段数据。
/// <para>
/// 注意:启用该功能需配合 <see cref="SensitivePropertyEncryptionSM4Key"/> 和 <see cref="SensitivePropertyEncryptionSM4IV"/> 使用。
/// </para>
/// </summary>
public bool AutoEncryptRequestSensitiveProperty { get; set; }
/// <summary> /// <summary>
/// 获取或设置是否自动解密响应中的敏感字段数据。 /// 获取或设置是否自动解密响应中的敏感字段数据。
/// </summary> /// </summary>

View File

@@ -1,4 +1,4 @@
using System; using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
{ {
@@ -6,7 +6,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
/// 表示微企付 API 回调通知事件的基类。 /// 表示微企付 API 回调通知事件的基类。
/// </summary> /// </summary>
[Serializable] [Serializable]
public class WechatTenpayBusinessEvent public class WechatTenpayBusinessEvent : ICommonWebhookEvent
{ {
/// <summary> /// <summary>
/// 获取或设置 API 版本。 /// 获取或设置 API 版本。
@@ -48,9 +48,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
/// 获取或设置通知创建时间。 /// 获取或设置通知创建时间。
/// </summary> /// </summary>
[Newtonsoft.Json.JsonProperty("create_time")] [Newtonsoft.Json.JsonProperty("create_time")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339DateTimeOffsetConverter))] [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.Common.Rfc3339DateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("create_time")] [System.Text.Json.Serialization.JsonPropertyName("create_time")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339DateTimeOffsetConverter))] [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.Common.Rfc3339DateTimeOffsetConverter))]
public DateTimeOffset CreateTime { get; set; } public DateTimeOffset CreateTime { get; set; }
} }

View File

@@ -1,11 +1,11 @@
using System; using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
{ {
/// <summary> /// <summary>
/// 当调用微企付 API 出错时引发的异常。 /// 当调用微企付 API 出错时引发的异常。
/// </summary> /// </summary>
public class WechatTenpayBusinessException : CommonExceptionBase public class WechatTenpayBusinessException : CommonException
{ {
/// <inheritdoc/> /// <inheritdoc/>
public WechatTenpayBusinessException() public WechatTenpayBusinessException()

View File

@@ -3,22 +3,15 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
/// <summary> /// <summary>
/// 表示微企付 API 请求的基类。 /// 表示微企付 API 请求的基类。
/// </summary> /// </summary>
public abstract class WechatTenpayBusinessRequest : ICommonRequest public abstract class WechatTenpayBusinessRequest : CommonRequestBase, ICommonRequest
{ {
/// <summary>
/// 获取或设置请求超时时间(单位:毫秒)。如果不指定将使用构造 <see cref="WechatTenpayBusinessClient"/> 时的 <see cref="WechatTenpayBusinessClientOptions.Timeout"/> 参数,这在需要指定特定耗时请求(比如上传或下载文件)的超时时间时很有用。
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
public virtual int? Timeout { get; set; }
/// <summary> /// <summary>
/// 获取或设置请求使用的微企付敏感字段加密参数。 /// 获取或设置请求使用的微企付敏感字段加密参数。
/// <para>如果启用了 <see cref="WechatTenpayBusinessClientOptions.AutoEncryptRequestSensitiveProperty"/> 参数,将由系统自动生成。</para> /// <para>如果启用了 <see cref="WechatTenpayBusinessClientOptions.AutoEncryptRequestSensitiveProperty"/> 参数,将由系统自动生成。</para>
/// </summary> /// </summary>
[Newtonsoft.Json.JsonIgnore] [Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore] [System.Text.Json.Serialization.JsonIgnore]
public virtual WechatTenpayBusinessRequestEncryption? Encryption { get; set; } public virtual WechatTenpayBusinessRequestEncryption? WechatpayEncryption { get; set; }
} }
public sealed class WechatTenpayBusinessRequestEncryption public sealed class WechatTenpayBusinessRequestEncryption

View File

@@ -5,62 +5,14 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
/// <summary> /// <summary>
/// 表示微企付 API 响应的基类。 /// 表示微企付 API 响应的基类。
/// </summary> /// </summary>
public abstract class WechatTenpayBusinessResponse : ICommonResponse public abstract class WechatTenpayBusinessResponse : CommonResponseBase, ICommonResponse
{ {
/// <summary>
///
/// </summary>
int ICommonResponse.RawStatus { get; set; }
/// <summary>
///
/// </summary>
IDictionary<string, string> ICommonResponse.RawHeaders { get; set; } = default!;
/// <summary>
///
/// </summary>
byte[] ICommonResponse.RawBytes { get; set; } = default!;
/// <summary>
/// 获取原始的 HTTP 响应状态码。
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
public int RawStatus
{
get { return ((ICommonResponse)this).RawStatus; }
internal set { ((ICommonResponse)this).RawStatus = value; }
}
/// <summary>
/// 获取原始的 HTTP 响应表头集合。
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
public IDictionary<string, string> RawHeaders
{
get { return ((ICommonResponse)this).RawHeaders; }
internal set { ((ICommonResponse)this).RawHeaders = value; }
}
/// <summary>
/// 获取原始的 HTTP 响应正文。
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
public byte[] RawBytes
{
get { return ((ICommonResponse)this).RawBytes; }
internal set { ((ICommonResponse)this).RawBytes = value; }
}
/// <summary> /// <summary>
/// 获取微企付 API 返回的敏感字段加密参数。 /// 获取微企付 API 返回的敏感字段加密参数。
/// </summary> /// </summary>
[Newtonsoft.Json.JsonIgnore] [Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore] [System.Text.Json.Serialization.JsonIgnore]
public WechatTenpayBusinessResponseEncryption? Encryption { get; internal set; } public WechatTenpayBusinessResponseEncryption? WechatpayEncryption { get; internal set; }
/// <summary> /// <summary>
/// 获取微企付请求链路 ID。 /// 获取微企付请求链路 ID。
@@ -77,12 +29,15 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
public virtual WechatTenpayBusinessResponseError? Error { get; set; } public virtual WechatTenpayBusinessResponseError? Error { get; set; }
/// <summary> /// <summary>
/// 获取一个值,该值指示调用微企付 API 是否成功(即 HTTP 状态码为 200、202 或 204 /// 获取一个值,该值指示调用微企付 API 是否成功。
/// <para>
/// (即 HTTP 状态码为 200/202/204且 <see cref="Error.Code"/> 值为空)
/// </para>
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public virtual bool IsSuccessful() public override bool IsSuccessful()
{ {
return (RawStatus == 200 || RawStatus == 202 || RawStatus == 204) && string.IsNullOrEmpty(Error?.Code); return GetRawStatus() >= 200 && GetRawStatus() <= 204 && string.IsNullOrEmpty(Error?.Code);
} }
} }

View File

@@ -0,0 +1,56 @@
using System;
using System.IO;
using System.Reflection;
using SKIT.FlurlHttpClient.Tools.CodeAnalyzer;
using Xunit;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests
{
public class CodeAnalyzeTests
{
[Fact(DisplayName = "代码质量分析")]
public void TestCodeAnalyzer()
{
Assert.Null(Record.Exception(() =>
{
var options = new TypeDeclarationAnalyzerOptions()
{
SdkAssembly = Assembly.GetAssembly(typeof(WechatTenpayBusinessClient))!,
SdkRequestModelDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models",
SdkResponseModelDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models",
SdkExecutingExtensionDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.TenpayBusiness",
SdkWebhookEventDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Events",
ThrowOnNotFoundRequestModelTypes = true,
ThrowOnNotFoundResponseModelTypes = true,
ThrowOnNotFoundExecutingExtensionTypes = true,
ThrowOnNotFoundWebhookEventTypes = true
};
new TypeDeclarationAnalyzer(options).AssertNoIssues();
}));
Assert.Null(Record.Exception(() =>
{
string workdir = Environment.CurrentDirectory;
string projdir = Path.Combine(workdir, "../../../../../");
var options = new SourceFileAnalyzerOptions()
{
SdkAssembly = Assembly.GetAssembly(typeof(WechatTenpayBusinessClient))!,
SdkRequestModelDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models",
SdkResponseModelDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models",
SdkWebhookEventDeclarationNamespace = "SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Events",
ProjectSourceRootDirectory = Path.Combine(projdir, "./src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/"),
ProjectTestRootDirectory = Path.Combine(projdir, "./test/SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests/"),
ThrowOnNotFoundRequestModelClassCodeFiles = true,
ThrowOnNotFoundResponseModelClassCodeFiles = true,
ThrowOnNotFoundExecutingExtensionClassCodeFiles = true,
ThrowOnNotFoundWebhookEventClassCodeFiles = true,
ThrowOnNotFoundRequestModelSerializationSampleFiles = true,
ThrowOnNotFoundResponseModelSerializationSampleFiles = true,
ThrowOnNotFoundWebhookEventSerializationSampleFiles = true
};
new SourceFileAnalyzer(options).AssertNoIssues();
}));
}
}
}

View File

@@ -11,22 +11,22 @@
<ItemGroup> <ItemGroup>
<None Remove=".gitignore" /> <None Remove=".gitignore" />
<Content Include="appsettings.json"> <Content Include="appsettings.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</Content>
<Content Include="appsettings.*.json" Condition="'$(Configuration)' == 'Debug'">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content> </Content>
<Content Include="appsettings.*.json" Condition="'$(Configuration)' == 'Debug'">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
</Content>
<Content Include="ModelSamples/**/*.json" /> <Content Include="ModelSamples/**/*.json" />
<Content Include="EventSamples/**/*.json" /> <Content Include="EventSamples/**/*.json" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="SKIT.FlurlHttpClient.Tools.CodeAnalyzer" Version="0.1.0-alpha.1" /> <PackageReference Include="SKIT.FlurlHttpClient.Tools.CodeAnalyzer" Version="0.3.0-preview.1" />
<PackageReference Include="xunit" Version="2.4.2" /> <PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5"> <PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

View File

@@ -1,26 +0,0 @@
using SKIT.FlurlHttpClient.Tools.CodeAnalyzer;
using Xunit;
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests
{
public class TestCase_CodeReview
{
[Fact(DisplayName = "测试用例:代码质量分析")]
public void TestCodeAnalyzer()
{
Assert.Null(Record.Exception(() =>
{
CodeAnalyzerOptions options = new CodeAnalyzerOptions()
{
AssemblyName = "SKIT.FlurlHttpClient.Wechat.TenpayBusiness",
WorkDirectoryForSourceCode = TestConfigs.WorkDirectoryForSdk,
WorkDirectoryForTestSample = TestConfigs.WorkDirectoryForTest
};
CodeAnalyzer analyzer = new CodeAnalyzer(options);
analyzer.Start();
analyzer.Assert();
analyzer.Flush();
}));
}
}
}

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.IO; using System.IO;
using System.Text.Json; using System.Text.Json;
@@ -16,15 +16,12 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests
using var stream = File.OpenRead("appsettings.local.json"); using var stream = File.OpenRead("appsettings.local.json");
using var jdoc = JsonDocument.Parse(stream); using var jdoc = JsonDocument.Parse(stream);
var config = jdoc.RootElement.GetProperty("TestConfig"); var config = jdoc.RootElement.GetProperty("TestConfigs");
WechatPlatformId = config.GetProperty("PlatformId").GetString()!; WechatPlatformId = config.GetProperty("PlatformId").GetString()!;
WechatPlatformCertSerialNumber = config.GetProperty("PlatformCertSerialNumber").GetString()!; WechatPlatformCertSerialNumber = config.GetProperty("PlatformCertSerialNumber").GetString()!;
WechatPlatformCertPrivateKey = config.GetProperty("PlatformCertPrivateKey").GetString()!; WechatPlatformCertPrivateKey = config.GetProperty("PlatformCertPrivateKey").GetString()!;
WechatTBEPCertSerialNumber = config.GetProperty("TBEPCertSerialNumber").GetString()!; WechatTBEPCertSerialNumber = config.GetProperty("TBEPCertSerialNumber").GetString()!;
WechatTBEPCertPrivateKey = config.GetProperty("TBEPCertPrivateKey").GetString()!; WechatTBEPCertPrivateKey = config.GetProperty("TBEPCertPrivateKey").GetString()!;
WorkDirectoryForSdk = jdoc.RootElement.GetProperty("WorkDirectoryForSdk").GetString()!;
WorkDirectoryForTest = jdoc.RootElement.GetProperty("WorkDirectoryForTest").GetString()!;
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -37,8 +34,5 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests
public static readonly string WechatPlatformCertPrivateKey; public static readonly string WechatPlatformCertPrivateKey;
public static readonly string WechatTBEPCertSerialNumber; public static readonly string WechatTBEPCertSerialNumber;
public static readonly string WechatTBEPCertPrivateKey; public static readonly string WechatTBEPCertPrivateKey;
public static readonly string WorkDirectoryForSdk;
public static readonly string WorkDirectoryForTest;
} }
} }

View File

@@ -1,11 +1,9 @@
{ {
"TestConfig": { "TestConfigs": {
"PlatformId": "请在此填写用于测试的微企付商户号", "PlatformId": "请在此填写用于测试的微企付商户号",
"PlatformCertSerialNumber": "请在此填写用于测试的微企付平台 API 证书序列号", "PlatformCertSerialNumber": "请在此填写用于测试的微企付平台 API 证书序列号",
"PlatformCertPrivateKey": "请在此填写用于测试的微企付平台 API 证书私钥(字符串格式)", "PlatformCertPrivateKey": "请在此填写用于测试的微企付平台 API 证书私钥(字符串格式)",
"TBEPCertSerialNumber": "请在此填写用于测试的微企付证书序列号", "TBEPCertSerialNumber": "请在此填写用于测试的微企付证书序列号",
"TBEPCertPrivateKey": "请在此填写用于测试的微企付证书私钥(字符串格式)" "TBEPCertPrivateKey": "请在此填写用于测试的微企付证书私钥(字符串格式)"
}, }
"WorkDirectoryForSdk": "请输入当前 SDK 项目所在的目录完整路径,如 C:\\Project\\src\\SKIT.FlurlHttpClient.Wechat.TenpayBusiness\\",
"WorkDirectoryForTest": "请输入当前测试项目所在的目录完整路径,如 C:\\Project\\test\\SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests\\"
} }