mirror of
https://gitee.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat.git
synced 2025-07-15 14:04:32 +08:00
feat(tenpaybusiness): 导入项目
This commit is contained in:
parent
26b9c17a7d
commit
a0a90f4851
@ -16,6 +16,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.TenpayV3", "src\SKIT.FlurlHttpClient.Wechat.TenpayV3\SKIT.FlurlHttpClient.Wechat.TenpayV3.csproj", "{6FE502D4-C43D-49C9-9E57-D1EE566FD1C3}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.TenpayBusiness", "src\SKIT.FlurlHttpClient.Wechat.TenpayBusiness\SKIT.FlurlHttpClient.Wechat.TenpayBusiness.csproj", "{A0951B55-4C90-450C-A165-FBDD2096D668}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.Work", "src\SKIT.FlurlHttpClient.Wechat.Work\SKIT.FlurlHttpClient.Wechat.Work.csproj", "{CDD123E6-2622-4368-BAEE-8B95F05F1AB2}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.Ads", "src\SKIT.FlurlHttpClient.Wechat.Ads\SKIT.FlurlHttpClient.Wechat.Ads.csproj", "{7F155EFB-152F-4798-9984-99102B21D2F8}"
|
||||
@ -30,6 +32,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests", "test\SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests\SKIT.FlurlHttpClient.Wechat.TenpayV3.UnitTests.csproj", "{5ECE2E7A-9AE8-49BF-902D-41A7756C3E78}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests", "test\SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests\SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests.csproj", "{3ABC7209-CA5E-42C1-8EA2-1724C16EFC0A}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.Work.UnitTests", "test\SKIT.FlurlHttpClient.Wechat.Work.UnitTests\SKIT.FlurlHttpClient.Wechat.Work.UnitTests.csproj", "{DBF84F66-1436-4599-93AB-7C16A3A2C3A4}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.Ads.UnitTests", "test\SKIT.FlurlHttpClient.Wechat.Ads.UnitTests\SKIT.FlurlHttpClient.Wechat.Ads.UnitTests.csproj", "{561E0BFB-7817-41FE-BAF2-D78817679AC1}"
|
||||
@ -62,6 +66,10 @@ Global
|
||||
{6FE502D4-C43D-49C9-9E57-D1EE566FD1C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6FE502D4-C43D-49C9-9E57-D1EE566FD1C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6FE502D4-C43D-49C9-9E57-D1EE566FD1C3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A0951B55-4C90-450C-A165-FBDD2096D668}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A0951B55-4C90-450C-A165-FBDD2096D668}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A0951B55-4C90-450C-A165-FBDD2096D668}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A0951B55-4C90-450C-A165-FBDD2096D668}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CDD123E6-2622-4368-BAEE-8B95F05F1AB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CDD123E6-2622-4368-BAEE-8B95F05F1AB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CDD123E6-2622-4368-BAEE-8B95F05F1AB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@ -86,6 +94,10 @@ Global
|
||||
{5ECE2E7A-9AE8-49BF-902D-41A7756C3E78}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5ECE2E7A-9AE8-49BF-902D-41A7756C3E78}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5ECE2E7A-9AE8-49BF-902D-41A7756C3E78}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3ABC7209-CA5E-42C1-8EA2-1724C16EFC0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3ABC7209-CA5E-42C1-8EA2-1724C16EFC0A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3ABC7209-CA5E-42C1-8EA2-1724C16EFC0A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3ABC7209-CA5E-42C1-8EA2-1724C16EFC0A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DBF84F66-1436-4599-93AB-7C16A3A2C3A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DBF84F66-1436-4599-93AB-7C16A3A2C3A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DBF84F66-1436-4599-93AB-7C16A3A2C3A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@ -118,12 +130,14 @@ Global
|
||||
{082C1F69-7932-473F-A700-49584371BE8C} = {3E34ADB9-1F52-4C96-9A42-DE782DE1AAA3}
|
||||
{18DEF654-1EDF-46C7-8430-685D6236E9C5} = {3E34ADB9-1F52-4C96-9A42-DE782DE1AAA3}
|
||||
{6FE502D4-C43D-49C9-9E57-D1EE566FD1C3} = {3E34ADB9-1F52-4C96-9A42-DE782DE1AAA3}
|
||||
{A0951B55-4C90-450C-A165-FBDD2096D668} = {3E34ADB9-1F52-4C96-9A42-DE782DE1AAA3}
|
||||
{CDD123E6-2622-4368-BAEE-8B95F05F1AB2} = {3E34ADB9-1F52-4C96-9A42-DE782DE1AAA3}
|
||||
{7F155EFB-152F-4798-9984-99102B21D2F8} = {3E34ADB9-1F52-4C96-9A42-DE782DE1AAA3}
|
||||
{AAE2E9BC-4D0B-4495-8825-DF8C405DB4A0} = {3E34ADB9-1F52-4C96-9A42-DE782DE1AAA3}
|
||||
{0C87A7D9-26EA-4821-AF3F-6D28B3006B24} = {C95AF531-CF44-44AA-AC90-F4DF9F941674}
|
||||
{574A567A-6D2C-49F6-9A98-0133CA9B007D} = {C95AF531-CF44-44AA-AC90-F4DF9F941674}
|
||||
{5ECE2E7A-9AE8-49BF-902D-41A7756C3E78} = {C95AF531-CF44-44AA-AC90-F4DF9F941674}
|
||||
{3ABC7209-CA5E-42C1-8EA2-1724C16EFC0A} = {C95AF531-CF44-44AA-AC90-F4DF9F941674}
|
||||
{DBF84F66-1436-4599-93AB-7C16A3A2C3A4} = {C95AF531-CF44-44AA-AC90-F4DF9F941674}
|
||||
{561E0BFB-7817-41FE-BAF2-D78817679AC1} = {C95AF531-CF44-44AA-AC90-F4DF9F941674}
|
||||
{9689135B-44BA-4D55-8663-7C669BAFE066} = {C95AF531-CF44-44AA-AC90-F4DF9F941674}
|
||||
|
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class WechatTenpayBusinessSensitiveAttribute : Attribute
|
||||
{
|
||||
public WechatTenpayBusinessSensitiveAttribute()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class WechatTenpayBusinessSensitivePropertyAttribute : Attribute
|
||||
{
|
||||
public WechatTenpayBusinessSensitivePropertyAttribute()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Constants
|
||||
{
|
||||
public static class EncryptionAlgorithms
|
||||
{
|
||||
public const string RSA_OAEP_WITH_SM4_128_CBC = "RSA_OAEP_with_SM4_128_CBC";
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Constants
|
||||
{
|
||||
internal static class FormDataFields
|
||||
{
|
||||
public const string FORMDATA_META = "meta";
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Constants
|
||||
{
|
||||
public static class SignAlgorithms
|
||||
{
|
||||
/// <summary>
|
||||
/// SHA256withRSA。
|
||||
/// </summary>
|
||||
public const string SHA245_WITH_RSA = "SHA256withRSA";
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
using System;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 pay.created 通知的数据。</para>
|
||||
/// <para>表示 pay.bank_accept 通知的数据。</para>
|
||||
/// <para>表示 pay.succeeded 通知的数据。</para>
|
||||
/// <para>表示 pay.failed 通知的数据。</para>
|
||||
/// <para>表示 pay.revoked 通知的数据。</para>
|
||||
/// </summary>
|
||||
public class MSEPayPaymentEvent : WechatTenpayBusinessEvent<MSEPayPaymentEvent.Types.EventContent>
|
||||
{
|
||||
public static class Types
|
||||
{
|
||||
public class EventContent
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置平台支付单号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("out_payment_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("out_payment_id")]
|
||||
public string OutPaymentId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置微企付支付单号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("payment_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("payment_id")]
|
||||
public string PaymentId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置支付金额(单位:分)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("amount")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("amount")]
|
||||
public int Amount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置付款方 ID。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("purchaser_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("purchaser_id")]
|
||||
public string PurchaserId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置收款方企业 ID。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("bargainor_ent_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("bargainor_ent_id")]
|
||||
public string BargainorEnterpriseId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置订单状态。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("status")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("status")]
|
||||
public string Status { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置失败原因。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("failed_reason")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("failed_reason")]
|
||||
public string? FailedReason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置支付成功时间。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("pay_succ_time")]
|
||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339NullableDateTimeOffsetConverter))]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("pay_succ_time")]
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339NullableDateTimeOffsetConverter))]
|
||||
public DateTimeOffset? PaySuccessTime { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 product.application.finish 通知的数据。</para>
|
||||
/// </summary>
|
||||
public class ProductApplicationEvent : WechatTenpayBusinessEvent<ProductApplicationEvent.Types.EventContent>
|
||||
{
|
||||
public static class Types
|
||||
{
|
||||
public class EventContent
|
||||
{
|
||||
public static class Types
|
||||
{
|
||||
public class Product
|
||||
{
|
||||
public static class Types
|
||||
{
|
||||
public class Account
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置企业账户 ID。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("ent_acct_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("ent_acct_id")]
|
||||
public string EnterpriseAccountId { get; set; } = default!;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置产品名称。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("product_name")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("product_name")]
|
||||
public string ProductName { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置开通状态。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("status")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("status")]
|
||||
public string Status { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置账户列表。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("accounts")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("accounts")]
|
||||
public Types.Account[]? AccountList { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置业务申请编号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("out_request_no")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("out_request_no")]
|
||||
public string OutRequestNumber { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置微企付开户申请单号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("request_no")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("request_no")]
|
||||
public string RequestNumber { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置企业 ID。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("ent_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("ent_id")]
|
||||
public string EnterpriseId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置开通状态。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("status")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("status")]
|
||||
public string Status { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置开通产品列表。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("product_details")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("product_details")]
|
||||
public Types.Product[] ProductList { get; set; } = default!;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
internal static class WechatTenpayBusinessClientSignExtensions
|
||||
{
|
||||
public static bool VerifySignature(this WechatTenpayBusinessClient client, string strAuthorization, string strBody)
|
||||
{
|
||||
return VerifySignature(client, strAuthorization, strBody, out _);
|
||||
}
|
||||
|
||||
public static bool VerifySignature(this WechatTenpayBusinessClient client, string strAuthorization, string strBody, out Exception? error)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(strAuthorization))
|
||||
{
|
||||
try
|
||||
{
|
||||
IDictionary<string, string?> dictTBEPAuthorization = strAuthorization
|
||||
.Split(',')
|
||||
.Select(s => s.Trim().Split(new char[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries))
|
||||
.ToDictionary(
|
||||
k => k[0],
|
||||
v => v.Length > 1 ? v[1].TrimStart('\"').TrimEnd('\"') : null
|
||||
);
|
||||
string strTimestamp = dictTBEPAuthorization["timestamp"]!;
|
||||
string strNonce = dictTBEPAuthorization["nonce"]!;
|
||||
string strSignature = dictTBEPAuthorization["signature"]!;
|
||||
string strSerialNumber = dictTBEPAuthorization["tbep_serial_number"]!;
|
||||
string strSignAlgorithm = dictTBEPAuthorization["signature_algorithm"]!;
|
||||
|
||||
return VerifySignature(client, strTimestamp, strNonce, strBody, strSignature, strSerialNumber, strSignAlgorithm, out error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
error = new Exception("Could not read value of `TBEP-Authorization`.");
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool VerifySignature(this WechatTenpayBusinessClient client, string strTimestamp, string strNonce, string strBody, string strSignature, string strSerialNumber)
|
||||
{
|
||||
return VerifySignature(client, strTimestamp, strNonce, strBody, strSignature, strSerialNumber, Constants.SignAlgorithms.SHA245_WITH_RSA, out _);
|
||||
}
|
||||
|
||||
public static bool VerifySignature(this WechatTenpayBusinessClient client, string strTimestamp, string strNonce, string strBody, string strSignature, string strSerialNumber, string strSignAlgorithm)
|
||||
{
|
||||
return VerifySignature(client, strTimestamp, strNonce, strBody, strSignature, strSerialNumber, strSignAlgorithm, out _);
|
||||
}
|
||||
|
||||
public static bool VerifySignature(this WechatTenpayBusinessClient client, string strTimestamp, string strNonce, string strBody, string strSignature, string strSerialNumber, out Exception? error)
|
||||
{
|
||||
return VerifySignature(client, strTimestamp, strNonce, strBody, strSignature, strSerialNumber, Constants.SignAlgorithms.SHA245_WITH_RSA, out error);
|
||||
}
|
||||
|
||||
public static bool VerifySignature(this WechatTenpayBusinessClient client, string strTimestamp, string strNonce, string strBody, string strSignature, string strSerialNumber, string strSignAlgorithm, out Exception? error)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
|
||||
switch (strSignAlgorithm)
|
||||
{
|
||||
case Constants.SignAlgorithms.SHA245_WITH_RSA:
|
||||
{
|
||||
if (client.Credentials.TBEPCertificateSerialNumber != null &&
|
||||
client.Credentials.TBEPCertificatePublicKey != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.Equals(client.Credentials.TBEPCertificateSerialNumber, strSerialNumber))
|
||||
{
|
||||
error = new Exception("There is no TBEP public key matched the serial number.");
|
||||
return false;
|
||||
}
|
||||
|
||||
error = null;
|
||||
return Utilities.RSAUtility.VerifyWithSHA256(
|
||||
publicKey: client.Credentials.TBEPCertificatePublicKey,
|
||||
plainText: GetPlainTextForSignature(timestamp: strTimestamp, nonce: strNonce, body: strBody),
|
||||
signature: strSignature
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
error = new Exception("There is no TBEP public key or serial number.");
|
||||
return false;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
error = new Exception("Unsupported sign algorithm.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPlainTextForSignature(string timestamp, string nonce, string body)
|
||||
{
|
||||
return $"{timestamp}\n{nonce}\n{body}\n";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
using System;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
public static class WechatTenpayBusinessClientEventDeserializationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>反序列化得到 <see cref="WechatTenpayBusinessEvent"/> 对象。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="callbackJson"></param>
|
||||
/// <returns></returns>
|
||||
public static WechatTenpayBusinessEvent DeserializeEvent(this WechatTenpayBusinessClient client, string callbackJson)
|
||||
{
|
||||
return DeserializeEvent<WechatTenpayBusinessEvent>(client, callbackJson);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>反序列化得到 <see cref="WechatTenpayBusinessEvent"/> 对象。</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TEvent"></typeparam>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="callbackJson"></param>
|
||||
/// <returns></returns>
|
||||
public static TEvent DeserializeEvent<TEvent>(this WechatTenpayBusinessClient client, string callbackJson)
|
||||
where TEvent : WechatTenpayBusinessEvent
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (string.IsNullOrEmpty(callbackJson)) throw new ArgumentNullException(callbackJson);
|
||||
|
||||
return client.JsonSerializer.Deserialize<TEvent>(callbackJson);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
public static class WechatTenpayBusinessClientEventVerificationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>验证回调通知事件签名。</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse"></typeparam>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="callbackAuthorization"></param>
|
||||
/// <param name="callbackBody"></param>
|
||||
/// <returns></returns>
|
||||
public static bool VerifyEventSignature(this WechatTenpayBusinessClient client, string callbackAuthorization, string callbackBody)
|
||||
{
|
||||
return VerifyEventSignature(client, callbackAuthorization, callbackBody, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>验证回调通知事件签名。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="callbackAuthorization"></param>
|
||||
/// <param name="callbackBody"></param>
|
||||
/// <param name="error"></param>
|
||||
/// <returns></returns>
|
||||
public static bool VerifyEventSignature(this WechatTenpayBusinessClient client, string callbackAuthorization, string callbackBody, out Exception? error)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (callbackAuthorization == null) throw new ArgumentNullException(nameof(callbackAuthorization));
|
||||
if (callbackBody == null) throw new ArgumentNullException(nameof(callbackBody));
|
||||
|
||||
bool ret = WechatTenpayBusinessClientSignExtensions.VerifySignature(client, callbackAuthorization, callbackBody, out error);
|
||||
if (error != null)
|
||||
error = new Exceptions.WechatTenpayBusinessEventVerificationException("Verify signature of event failed. Please see the `InnerException` for more details.", error);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>验证回调通知事件签名。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="callbackTimestamp"></param>
|
||||
/// <param name="callbackNonce">。</param>
|
||||
/// <param name="callbackBody"></param>
|
||||
/// <param name="callbackSignature"></param>
|
||||
/// <param name="callbackSerialNumber"></param>
|
||||
/// <returns></returns>
|
||||
public static bool VerifyEventSignature(this WechatTenpayBusinessClient client, string callbackTimestamp, string callbackNonce, string callbackBody, string callbackSignature, string callbackSerialNumber)
|
||||
{
|
||||
return VerifyEventSignature(client, callbackTimestamp, callbackNonce, callbackBody, callbackSignature, callbackSerialNumber, Constants.SignAlgorithms.SHA245_WITH_RSA, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>验证回调通知事件签名。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="callbackTimestamp"></param>
|
||||
/// <param name="callbackNonce">。</param>
|
||||
/// <param name="callbackBody"></param>
|
||||
/// <param name="callbackSignature"></param>
|
||||
/// <param name="callbackSerialNumber"></param>
|
||||
/// <param name="callbackSignAlgorithm"></param>
|
||||
/// <returns></returns>
|
||||
public static bool VerifyEventSignature(this WechatTenpayBusinessClient client, string callbackTimestamp, string callbackNonce, string callbackBody, string callbackSignature, string callbackSerialNumber, string callbackSignAlgorithm)
|
||||
{
|
||||
return VerifyEventSignature(client, callbackTimestamp, callbackNonce, callbackBody, callbackSignature, callbackSerialNumber, callbackSignAlgorithm, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>验证回调通知事件签名。</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse"></typeparam>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="callbackTimestamp"></param>
|
||||
/// <param name="callbackNonce">。</param>
|
||||
/// <param name="callbackBody"></param>
|
||||
/// <param name="callbackSignature"></param>
|
||||
/// <param name="callbackSerialNumber"></param>
|
||||
/// <param name="error"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public static bool VerifyEventSignature(this WechatTenpayBusinessClient client, string callbackTimestamp, string callbackNonce, string callbackBody, string callbackSignature, string callbackSerialNumber, out Exception? error)
|
||||
{
|
||||
return VerifyEventSignature(client, callbackTimestamp, callbackNonce, callbackBody, callbackSignature, callbackSerialNumber, Constants.SignAlgorithms.SHA245_WITH_RSA, out error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>验证回调通知事件签名。</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse"></typeparam>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="callbackTimestamp"></param>
|
||||
/// <param name="callbackNonce">。</param>
|
||||
/// <param name="callbackBody"></param>
|
||||
/// <param name="callbackSignature"></param>
|
||||
/// <param name="callbackSerialNumber"></param>
|
||||
/// <param name="callbackSignAlgorithm"></param>
|
||||
/// <param name="error"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public static bool VerifyEventSignature(this WechatTenpayBusinessClient client, string callbackTimestamp, string callbackNonce, string callbackBody, string callbackSignature, string callbackSerialNumber, string callbackSignAlgorithm, out Exception? error)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
|
||||
bool ret = WechatTenpayBusinessClientSignExtensions.VerifySignature(client, callbackTimestamp, callbackNonce, callbackBody, callbackSignature, callbackSerialNumber, callbackSignAlgorithm, out error);
|
||||
if (error != null)
|
||||
error = new Exceptions.WechatTenpayBusinessEventVerificationException("Verify signature of event failed. Please see the `InnerException` for more details.", error);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Flurl.Http;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
public static class WechatTenpayBusinessClientExecuteFileUploadsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>异步调用 [POST] /file-uploads 接口。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Models.UploadFileResponse> ExecuteUploadFileAsync(this WechatTenpayBusinessClient client, Models.UploadFileRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||
|
||||
if (request.FileName == null)
|
||||
request.FileName = Guid.NewGuid().ToString("N").ToLower() + ".png";
|
||||
|
||||
if (request.FileHash == null)
|
||||
request.FileHash = BitConverter.ToString(Utilities.SM3Utility.Hash(request.FileBytes)).Replace("-", "").ToLower();
|
||||
|
||||
if (request.FileContentType == null)
|
||||
request.FileContentType = Utilities.FileNameToContentTypeMapper.GetContentTypeForImage(request.FileName!) ?? "image/png";
|
||||
|
||||
IFlurlRequest flurlReq = client
|
||||
.CreateRequest(request, HttpMethod.Post, "file-uploads");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Flurl.Http;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
public static class WechatTenpayBusinessClientExecuteMSEPayAccountsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>异步调用 [POST] /mse-pay/accounts/mse-pay/{platform_id}/bill 接口。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Models.GetMSEPayAccountBillResponse> ExecuteGetMSEPayAccountBillAsync(this WechatTenpayBusinessClient client, Models.GetMSEPayAccountBillRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||
|
||||
IFlurlRequest flurlReq = client
|
||||
.CreateRequest(request, HttpMethod.Post, "mse-pay", "accounts", "mse-pay", client.Credentials.PlatformId, "bill");
|
||||
|
||||
return await client.SendRequestWithJsonAsync<Models.GetMSEPayAccountBillResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>异步调用 [GET] /{download_url} 接口。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Models.DownloadMSEPayAccountBillResponse> ExecuteDownloadMSEPayAccountBillAsync(this WechatTenpayBusinessClient client, Models.DownloadMSEPayAccountBillRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||
|
||||
IFlurlRequest flurlReq = client
|
||||
.CreateRequest(request, HttpMethod.Get, request.DownloadUrl)
|
||||
.WithUrl(request.DownloadUrl);
|
||||
|
||||
return await client.SendRequestWithJsonAsync<Models.DownloadMSEPayAccountBillResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Flurl.Http;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
public static class WechatTenpayBusinessClientExecuteMSEPayPaymentsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>异步调用 [POST] /mse-pay/payments/h5-pay 接口。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Models.CreateMSEPayPaymentH5PayResponse> ExecuteCreateMSEPayPaymentH5PayAsync(this WechatTenpayBusinessClient client, Models.CreateMSEPayPaymentH5PayRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||
|
||||
IFlurlRequest flurlReq = client
|
||||
.CreateRequest(request, HttpMethod.Post, "mse-pay", "payments", "h5-pay");
|
||||
|
||||
return await client.SendRequestWithJsonAsync<Models.CreateMSEPayPaymentH5PayResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>异步调用 [GET] /mse-pay/payments/out-payment-id/{out_payment_id} 接口。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Models.GetMSEPayPaymentByOutPaymentIdResponse> ExecuteGetMSEPayPaymentByOutPaymentIdAsync(this WechatTenpayBusinessClient client, Models.GetMSEPayPaymentByOutPaymentIdRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||
|
||||
IFlurlRequest flurlReq = client
|
||||
.CreateRequest(request, HttpMethod.Get, "mse-pay", "payments", "out-payment-id", request.OutPaymentId);
|
||||
|
||||
return await client.SendRequestWithJsonAsync<Models.GetMSEPayPaymentByOutPaymentIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>异步调用 [GET] /mse-pay/payments/{payment_id} 接口。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Models.GetMSEPayPaymentByPaymentIdResponse> ExecuteGetMSEPayPaymentByPaymentIdAsync(this WechatTenpayBusinessClient client, Models.GetMSEPayPaymentByPaymentIdRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||
|
||||
IFlurlRequest flurlReq = client
|
||||
.CreateRequest(request, HttpMethod.Get, "mse-pay", "payments", request.PaymentId);
|
||||
|
||||
return await client.SendRequestWithJsonAsync<Models.GetMSEPayPaymentByPaymentIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>异步调用 [POST] /mse-pay/payments/{payment_id}/close 接口。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Models.CloseMSEPayPaymentResponse> ExecuteCloseMSEPayPaymentAsync(this WechatTenpayBusinessClient client, Models.CloseMSEPayPaymentRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||
|
||||
IFlurlRequest flurlReq = client
|
||||
.CreateRequest(request, HttpMethod.Post, "mse-pay", "payments", request.PaymentId, "close");
|
||||
|
||||
return await client.SendRequestWithJsonAsync<Models.CloseMSEPayPaymentResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Flurl.Http;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
public static class WechatTenpayBusinessClientExecuteMSEPayProductApplicationsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>异步调用 [POST] /mse-pay/product-applications 接口。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Models.CreateMSEPayProductApplicationResponse> ExecuteCreateMSEPayProductApplicationAsync(this WechatTenpayBusinessClient client, Models.CreateMSEPayProductApplicationRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||
|
||||
IFlurlRequest flurlReq = client
|
||||
.CreateRequest(request, HttpMethod.Post, "mse-pay", "product-applications");
|
||||
|
||||
return await client.SendRequestWithJsonAsync<Models.CreateMSEPayProductApplicationResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>异步调用 [GET] /mse-pay/product-applications/out-request-no/{out_request_no} 接口。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Models.GetMSEPayProductApplicationByOutRequestNumberResponse> ExecuteGetMSEPayProductApplicationByOutRequestNumberAsync(this WechatTenpayBusinessClient client, Models.GetMSEPayProductApplicationByOutRequestNumberRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||
|
||||
IFlurlRequest flurlReq = client
|
||||
.CreateRequest(request, HttpMethod.Get, "mse-pay", "product-applications", "out-request-no", request.OutRequestNumber);
|
||||
|
||||
return await client.SendRequestWithJsonAsync<Models.GetMSEPayProductApplicationByOutRequestNumberResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>异步调用 [GET] /mse-pay/product-applications/{request_no} 接口。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Models.GetMSEPayProductApplicationByRequestNumberResponse> ExecuteGetMSEPayProductApplicationByRequestNumberAsync(this WechatTenpayBusinessClient client, Models.GetMSEPayProductApplicationByRequestNumberRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||
|
||||
IFlurlRequest flurlReq = client
|
||||
.CreateRequest(request, HttpMethod.Get, "mse-pay", "product-applications", request.RequestNumber);
|
||||
|
||||
return await client.SendRequestWithJsonAsync<Models.GetMSEPayProductApplicationByRequestNumberResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>异步调用 [POST] /mse-pay/product-applications/{request_no}/links 接口。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Models.CreateMSEPayProductApplicationLinkResponse> ExecuteCreateMSEPayProductApplicationLinkAsync(this WechatTenpayBusinessClient client, Models.CreateMSEPayProductApplicationLinkRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||
|
||||
IFlurlRequest flurlReq = client
|
||||
.CreateRequest(request, HttpMethod.Post, "mse-pay", "product-applications", request.RequestNumber, "links");
|
||||
|
||||
return await client.SendRequestWithJsonAsync<Models.CreateMSEPayProductApplicationLinkResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Flurl.Http;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
public static class WechatTenpayBusinessClientExecuteMSEPayRedirectsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>异步调用 [POST] /mse-pay/redirects 接口。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<Models.CreateMSEPayRedirectLinkResponse> ExecuteCreateMSEPayRedirectLinkAsync(this WechatTenpayBusinessClient client, Models.CreateMSEPayRedirectLinkRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (client is null) throw new ArgumentNullException(nameof(client));
|
||||
if (request is null) throw new ArgumentNullException(nameof(request));
|
||||
|
||||
IFlurlRequest flurlReq = client
|
||||
.CreateRequest(request, HttpMethod.Post, "mse-pay", "redirects");
|
||||
|
||||
return await client.SendRequestWithJsonAsync<Models.CreateMSEPayRedirectLinkResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
public static class WechatTenpayBusinessClientRequestEncryptionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>加密请求中传入的敏感数据。该方法会改变传入的请求模型对象。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <returns></returns>
|
||||
public static TRequest EncryptRequestSensitiveProperty<TRequest>(this WechatTenpayBusinessClient client, TRequest request)
|
||||
where TRequest : WechatTenpayBusinessRequest
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (request == null) throw new ArgumentNullException(nameof(request));
|
||||
|
||||
try
|
||||
{
|
||||
bool requireEncrypt = request.GetType().GetCustomAttributes<WechatTenpayBusinessSensitiveAttribute>(inherit: true).Any();
|
||||
if (requireEncrypt)
|
||||
{
|
||||
if (request.TBEPEncryption is null)
|
||||
request.TBEPEncryption = new WechatTenpayBusinessRequestTBEPEncryption() { Algorithm = client.Credentials.SensitivePropertyEncryptionAlgorithm };
|
||||
|
||||
if (Constants.EncryptionAlgorithms.RSA_OAEP_WITH_SM4_128_CBC.Equals(request.TBEPEncryption.Algorithm))
|
||||
{
|
||||
Utilities.ReflectionUtility.ReplacePropertyStringValue(ref request, (target, currentProp, oldValue) =>
|
||||
{
|
||||
var attr = currentProp.GetCustomAttribute<WechatTenpayBusinessSensitivePropertyAttribute>();
|
||||
if (attr == null)
|
||||
return (false, oldValue);
|
||||
|
||||
string sm4IV = client.Credentials.SensitivePropertyEncryptionSM4IV!;
|
||||
string sm4Key = client.Credentials.SensitivePropertyEncryptionSM4Key!;
|
||||
string sm4EncryptedKey = Utilities.RSAUtility.EncryptWithECB(publicKey: client.Credentials.TBEPCertificatePublicKey, plainText: sm4Key);
|
||||
|
||||
request.TBEPEncryption.CertificateSerialNumber = client.Credentials.TBEPCertificateSerialNumber;
|
||||
request.TBEPEncryption.EncryptedKey = sm4EncryptedKey;
|
||||
request.TBEPEncryption.IV = sm4IV;
|
||||
|
||||
string newValue = Utilities.SM4Utility.EncryptWithCBC(key: sm4Key, iv: sm4IV, plainText: oldValue);
|
||||
return (true, newValue);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Unsupported encryption algorithm.");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!(ex is Exceptions.WechatTenpayBusinessRequestEncryptionException))
|
||||
{
|
||||
throw new Exceptions.WechatTenpayBusinessRequestEncryptionException("Encrypt request failed. Please see the `InnerException` for more details.", ex);
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
public static class WechatTenpayBusinessClientResponseDecryptionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>解密响应中返回的敏感数据。该方法会改变传入的响应模型对象。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="response"></param>
|
||||
/// <returns></returns>
|
||||
public static TResponse DecryptResponseSensitiveProperty<TResponse>(this WechatTenpayBusinessClient client, TResponse response)
|
||||
where TResponse : WechatTenpayBusinessResponse
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
if (response == null) throw new ArgumentNullException(nameof(response));
|
||||
|
||||
if (!response.IsSuccessful())
|
||||
throw new Exceptions.WechatTenpayBusinessResponseDecryptionException("Decrypt response failed, because the response is not successful.");
|
||||
|
||||
try
|
||||
{
|
||||
bool requireDecrypt = response.GetType().GetCustomAttributes<WechatTenpayBusinessSensitiveAttribute>(inherit: true).Any();
|
||||
if (requireDecrypt)
|
||||
{
|
||||
if (response.TBEPEncryption is null)
|
||||
throw new InvalidOperationException("Could not read value of `TBEP-Encrypt`.");
|
||||
if (response.TBEPEncryption.CertificateSerialNumber != client.Credentials.PlatformCertificateSerialNumber)
|
||||
throw new Exceptions.WechatTenpayBusinessResponseDecryptionException("Decrypt response failed, because there is no platform certificate matched the serial number.");
|
||||
|
||||
if (Constants.EncryptionAlgorithms.RSA_OAEP_WITH_SM4_128_CBC.Equals(response.TBEPEncryption.Algorithm))
|
||||
{
|
||||
Utilities.ReflectionUtility.ReplacePropertyStringValue(ref response, (target, currentProp, oldValue) =>
|
||||
{
|
||||
var attr = currentProp.GetCustomAttribute<WechatTenpayBusinessSensitivePropertyAttribute>();
|
||||
if (attr == null)
|
||||
return (false, oldValue);
|
||||
|
||||
string sm4EncryptedKey = response.TBEPEncryption.EncryptedKey!;
|
||||
string sm4Key = Utilities.RSAUtility.DecryptWithECB(privateKey: client.Credentials.PlatformCertificatePrivateKey, cipherText: sm4EncryptedKey);
|
||||
string sm4IV = response.TBEPEncryption.IV!;
|
||||
|
||||
string newValue = Utilities.SM4Utility.DecryptWithCBC(key: sm4Key, iv: sm4IV, cipherText: oldValue);
|
||||
return (true, newValue);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Unsupported decryption algorithm.");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (!(ex is Exceptions.WechatTenpayBusinessResponseDecryptionException))
|
||||
{
|
||||
throw new Exceptions.WechatTenpayBusinessResponseDecryptionException("Decrypt response failed. Please see the `InnerException` for more details.", ex);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
public static class WechatTenpayBusinessClientResponseVerificationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>验证响应签名。</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse"></typeparam>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="response"></param>
|
||||
/// <returns></returns>
|
||||
public static bool VerifyResponseSignature<TResponse>(this WechatTenpayBusinessClient client, TResponse response)
|
||||
where TResponse : WechatTenpayBusinessResponse
|
||||
{
|
||||
return VerifyResponseSignature(client, response, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>验证响应签名。</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse"></typeparam>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="response"></param>
|
||||
/// <param name="error"></param>
|
||||
/// <returns></returns>
|
||||
public static bool VerifyResponseSignature<TResponse>(this WechatTenpayBusinessClient client, TResponse response, out Exception? error)
|
||||
where TResponse : WechatTenpayBusinessResponse
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
|
||||
string? responseAuthHeader = response.RawHeaders.FirstOrDefault(e => string.Equals(e.Key, "TBEP-Authorization", StringComparison.OrdinalIgnoreCase)).Value;
|
||||
string responseBody = Encoding.UTF8.GetString(response.RawBytes);
|
||||
bool ret = WechatTenpayBusinessClientSignExtensions.VerifySignature(client, responseAuthHeader, responseBody, out error);
|
||||
if (error != null)
|
||||
error = new Exceptions.WechatTenpayBusinessResponseVerificationException("Verify signature of response failed. Please see the `InnerException` for more details.", error);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>验证响应签名。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="responseTimestamp"></param>
|
||||
/// <param name="responseNonce">。</param>
|
||||
/// <param name="responseBody"></param>
|
||||
/// <param name="responseSignature"></param>
|
||||
/// <param name="responseSerialNumber"></param>
|
||||
/// <returns></returns>
|
||||
public static bool VerifyResponseSignature(this WechatTenpayBusinessClient client, string responseTimestamp, string responseNonce, string responseBody, string responseSignature, string responseSerialNumber)
|
||||
{
|
||||
return VerifyResponseSignature(client, responseTimestamp, responseNonce, responseBody, responseSignature, responseSerialNumber, Constants.SignAlgorithms.SHA245_WITH_RSA, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>验证响应签名。</para>
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="responseTimestamp"></param>
|
||||
/// <param name="responseNonce">。</param>
|
||||
/// <param name="responseBody"></param>
|
||||
/// <param name="responseSignature"></param>
|
||||
/// <param name="responseSerialNumber"></param>
|
||||
/// <param name="responseSignAlgorithm"></param>
|
||||
/// <returns></returns>
|
||||
public static bool VerifyResponseSignature(this WechatTenpayBusinessClient client, string responseTimestamp, string responseNonce, string responseBody, string responseSignature, string responseSerialNumber, string responseSignAlgorithm)
|
||||
{
|
||||
return VerifyResponseSignature(client, responseTimestamp, responseNonce, responseBody, responseSignature, responseSerialNumber, responseSignAlgorithm, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>验证响应签名。</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse"></typeparam>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="responseTimestamp"></param>
|
||||
/// <param name="responseNonce">。</param>
|
||||
/// <param name="responseBody"></param>
|
||||
/// <param name="responseSignature"></param>
|
||||
/// <param name="responseSerialNumber"></param>
|
||||
/// <param name="error"></param>
|
||||
/// <returns></returns>
|
||||
public static bool VerifyResponseSignature(this WechatTenpayBusinessClient client, string responseTimestamp, string responseNonce, string responseBody, string responseSignature, string responseSerialNumber, out Exception? error)
|
||||
{
|
||||
return VerifyResponseSignature(client, responseTimestamp, responseNonce, responseBody, responseSignature, responseSerialNumber, Constants.SignAlgorithms.SHA245_WITH_RSA, out error);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>验证响应签名。</para>
|
||||
/// </summary>
|
||||
/// <typeparam name="TResponse"></typeparam>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="responseTimestamp"></param>
|
||||
/// <param name="responseNonce">。</param>
|
||||
/// <param name="responseBody"></param>
|
||||
/// <param name="responseSignature"></param>
|
||||
/// <param name="responseSerialNumber"></param>
|
||||
/// <param name="responseSignAlgorithm"></param>
|
||||
/// <param name="error"></param>
|
||||
/// <returns></returns>
|
||||
public static bool VerifyResponseSignature(this WechatTenpayBusinessClient client, string responseTimestamp, string responseNonce, string responseBody, string responseSignature, string responseSerialNumber, string responseSignAlgorithm, out Exception? error)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
|
||||
bool ret = WechatTenpayBusinessClientSignExtensions.VerifySignature(client, responseTimestamp, responseNonce, responseBody, responseSignature, responseSerialNumber, responseSignAlgorithm, out error);
|
||||
if (error != null)
|
||||
error = new Exceptions.WechatTenpayBusinessResponseVerificationException("Verify signature of response failed. Please see the `InnerException` for more details.", error);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Flurl.Http;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Interceptors
|
||||
{
|
||||
internal class WechatTenpayBusinessRequestSignatureInterceptor : FlurlHttpCallInterceptor
|
||||
{
|
||||
private readonly string _signAlg;
|
||||
private readonly string _platformId;
|
||||
private readonly string _platformCertSn;
|
||||
private readonly string _platformCertPk;
|
||||
|
||||
public WechatTenpayBusinessRequestSignatureInterceptor(string signAlg, string platformId, string platformCertSn, string platformCertPk)
|
||||
{
|
||||
_signAlg = signAlg;
|
||||
_platformId = platformId;
|
||||
_platformCertSn = platformCertSn;
|
||||
_platformCertPk = platformCertPk;
|
||||
}
|
||||
|
||||
public override async Task BeforeCallAsync(FlurlCall flurlCall)
|
||||
{
|
||||
if (flurlCall == null) throw new ArgumentNullException(nameof(flurlCall));
|
||||
if (flurlCall.Completed) throw new Exceptions.WechatTenpayBusinessRequestSignatureException("This interceptor must be called before request completed.");
|
||||
|
||||
string method = flurlCall.HttpRequestMessage.Method.ToString().ToUpper();
|
||||
string url = flurlCall.HttpRequestMessage.RequestUri?.PathAndQuery ?? string.Empty;
|
||||
string timestamp = DateTimeOffset.Now.ToLocalTime().ToUnixTimeSeconds().ToString();
|
||||
string nonce = Guid.NewGuid().ToString("N");
|
||||
string body = string.Empty;
|
||||
|
||||
if (flurlCall.HttpRequestMessage.Content is MultipartFormDataContent formdataContent)
|
||||
{
|
||||
// NOTICE: multipart/form-data 文件上传请求的待签名参数需特殊处理
|
||||
var httpContent = formdataContent.SingleOrDefault(e => Constants.FormDataFields.FORMDATA_META.Equals(e.Headers.ContentDisposition?.Name?.Trim('\"')));
|
||||
if (httpContent != null)
|
||||
{
|
||||
body = await httpContent.ReadAsStringAsync();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
body = flurlCall.RequestBody ?? string.Empty;
|
||||
}
|
||||
|
||||
string plainText = $"{method}\n{url}\n{timestamp}\n{nonce}\n{body}\n";
|
||||
string signText;
|
||||
|
||||
switch (_signAlg)
|
||||
{
|
||||
case Constants.SignAlgorithms.SHA245_WITH_RSA:
|
||||
{
|
||||
try
|
||||
{
|
||||
signText = Utilities.RSAUtility.SignWithSHA256(_platformCertPk, plainText);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exceptions.WechatTenpayBusinessRequestSignatureException("Generate signature of request failed. Please see the `InnerException` for more details.", ex);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exceptions.WechatTenpayBusinessRequestSignatureException("Unsupported authorization sign algorithm.");
|
||||
}
|
||||
|
||||
string authString = $"platform_id=\"{_platformId}\",platform_serial_number=\"{_platformCertSn}\",nonce=\"{nonce}\",timestamp=\"{timestamp}\",signature=\"{signText}\",signature_algorithm=\"{_signAlg}\"";
|
||||
flurlCall.Request.Headers.Remove(FlurlHttpClient.Constants.HttpHeaders.Authorization);
|
||||
flurlCall.Request.WithHeader(FlurlHttpClient.Constants.HttpHeaders.Authorization, authString);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
using System;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [POST] /file-uploads 接口的请求。</para>
|
||||
/// </summary>
|
||||
public class UploadFileRequest : WechatTenpayBusinessRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置图片文件字节数组。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public byte[] FileBytes { get; set; } = Array.Empty<byte>();
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置图片文件名(必须以 jpg、bmp、png 为后缀)。如果不指定将由系统自动生成。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("filename")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("filename")]
|
||||
public string? FileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置图片文件摘要。如果不指定将由系统自动生成。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("sm3")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("sm3")]
|
||||
public string? FileHash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置图片文件 Conent-Type。如果不指定将由系统自动生成。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string? FileContentType { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [POST] /file-uploads 接口的响应。</para>
|
||||
/// </summary>
|
||||
public class UploadFileResponse : WechatTenpayBusinessResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置媒体文件标识 ID。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("file_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("file_id")]
|
||||
public string FileId { get; set; } = default!;
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [GET] /{download_url} 接口的请求。</para>
|
||||
/// </summary>
|
||||
public class DownloadMSEPayAccountBillRequest : WechatTenpayBusinessRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置文件下载链接。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string DownloadUrl { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [GET] /{download_url} 接口的响应。</para>
|
||||
/// </summary>
|
||||
public class DownloadMSEPayAccountBillResponse : WechatTenpayBusinessResponse
|
||||
{
|
||||
public override bool IsSuccessful()
|
||||
{
|
||||
return base.IsSuccessful() && RawBytes?.Length > 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [POST] /mse-pay/accounts/mse-pay/{platform_id}/bill 接口的请求。</para>
|
||||
/// </summary>
|
||||
public class GetMSEPayAccountBillRequest : WechatTenpayBusinessRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置查询日期字符串(格式:yyyy-MM-dd)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("query_date")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("query_date")]
|
||||
public string DateString { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
using System;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [POST] /mse-pay/accounts/mse-pay/{platform_id}/bill 接口的响应。</para>
|
||||
/// </summary>
|
||||
public class GetMSEPayAccountBillResponse : WechatTenpayBusinessResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置文件下载链接。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("download_url")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("download_url")]
|
||||
public string DownloadUrl { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置过期时间。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("expire_time")]
|
||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339DateTimeOffsetConverter))]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("expire_time")]
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339DateTimeOffsetConverter))]
|
||||
public DateTimeOffset ExpireTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置账单状态。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("bill_status")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("bill_status")]
|
||||
public string BillStatus { get; set; } = default!;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [POST] /mse-pay/payments/{payment_id}/close 接口的请求。</para>
|
||||
/// </summary>
|
||||
public class CloseMSEPayPaymentRequest : WechatTenpayBusinessRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置微企付支付单号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string PaymentId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置关闭原因。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("close_reason")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("close_reason")]
|
||||
public string CloseReason { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
using System;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [POST] /mse-pay/payments/{payment_id}/close 接口的响应。</para>
|
||||
/// </summary>
|
||||
public class CloseMSEPayPaymentResponse : WechatTenpayBusinessResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置平台支付单号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("out_payment_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("out_payment_id")]
|
||||
public string OutPaymentId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置微企付支付单号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("payment_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("payment_id")]
|
||||
public string PaymentId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置关单状态。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("status")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("status")]
|
||||
public string Status { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置关单时间。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("close_time")]
|
||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339DateTimeOffsetConverter))]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("close_time")]
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339DateTimeOffsetConverter))]
|
||||
public DateTimeOffset CloseTime { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,223 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [POST] /mse-pay/payments/h5-pay 接口的请求。</para>
|
||||
/// </summary>
|
||||
[WechatTenpayBusinessSensitive]
|
||||
public class CreateMSEPayPaymentH5PayRequest : WechatTenpayBusinessRequest
|
||||
{
|
||||
public static class Types
|
||||
{
|
||||
public class Payee
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置企业 ID。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("ent_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("ent_id")]
|
||||
public string EnterpriseId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置企业名称(需使用微企付公钥加密)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("ent_name")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("ent_name")]
|
||||
[WechatTenpayBusinessSensitiveProperty]
|
||||
public string EnterpriseName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置企业账户 ID。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("ent_acct_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("ent_acct_id")]
|
||||
public string EnterpriseAccountId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置银行卡号后 4 位。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("bank_account_number_last4")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("bank_account_number_last4")]
|
||||
public string? BankAccountNumberLast4String { get; set; }
|
||||
}
|
||||
|
||||
public class Goods
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置商品名称。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("good_name")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("good_name")]
|
||||
public string GoodsName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置商品数量。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("good_number")]
|
||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.TextualIntegerConverter))]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("good_number")]
|
||||
[System.Text.Json.Serialization.JsonNumberHandling(System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString | System.Text.Json.Serialization.JsonNumberHandling.WriteAsString)]
|
||||
public int Count { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置商品金额(单位:分)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("good_amount")]
|
||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.TextualIntegerConverter))]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("good_amount")]
|
||||
[System.Text.Json.Serialization.JsonNumberHandling(System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString | System.Text.Json.Serialization.JsonNumberHandling.WriteAsString)]
|
||||
public int Amount { get; set; }
|
||||
}
|
||||
|
||||
public class NotifyConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置支付结果通知 URL。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("server_notify_url")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("server_notify_url")]
|
||||
public string ServerNotifyUrl { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class RiskControl
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置设备号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("device_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("device_id")]
|
||||
public string? DeviceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置用户终端 IP。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("payer_client_ip")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("payer_client_ip")]
|
||||
public string? ClientIp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置用户浏览器 User-Agent。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("payer_ua")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("payer_ua")]
|
||||
public string? ClientUserAgent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置下单时间。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("create_time")]
|
||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339NullableDateTimeOffsetConverter))]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("create_time")]
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339NullableDateTimeOffsetConverter))]
|
||||
public DateTimeOffset? CreateTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置提货方式。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("pick_type")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("pick_type")]
|
||||
public string? PickType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置提货描述。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("pick_description")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("pick_description")]
|
||||
public string? PickDescription { get; set; }
|
||||
}
|
||||
|
||||
public class Promotion
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置付款类型。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("purchaser_type")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("purchaser_type")]
|
||||
public string PurchaserType { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置平台支付单号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("out_payment_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("out_payment_id")]
|
||||
public string OutPaymentId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置支付金额(单位:分)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("amount")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("amount")]
|
||||
public int Amount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置币种。
|
||||
/// <para>默认值:CNY</para>
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("currency")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("currency")]
|
||||
public string Currency { get; set; } = "CNY";
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置过期时间。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("expire_time")]
|
||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339DateTimeOffsetConverter))]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("expire_time")]
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339DateTimeOffsetConverter))]
|
||||
public DateTimeOffset ExpireTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置收款方信息。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("payee")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("payee")]
|
||||
public Types.Payee Payee { get; set; } = new Types.Payee();
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置附言。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("memo")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("memo")]
|
||||
public string? Memo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置商品信息列表。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("goods")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("goods")]
|
||||
public IList<Types.Goods>? GoodsList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置附加信息。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("attachment")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("attachment")]
|
||||
public string? Attachment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置风控信息。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("risk_control")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("risk_control")]
|
||||
public Types.RiskControl? RiskControl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置回调配置信息。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("notify_url")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("notify_url")]
|
||||
public Types.NotifyConfig NotifyConfig { get; set; } = new Types.NotifyConfig();
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置优惠信息。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("promotion_detail")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("promotion_detail")]
|
||||
public Types.Promotion? Promotion { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [POST] /mse-pay/payments/h5-pay 接口的响应。</para>
|
||||
/// </summary>
|
||||
public class CreateMSEPayPaymentH5PayResponse : GetMSEPayPaymentByPaymentIdResponse
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [GET] /mse-pay/payments/out-payment-id/{out_payment_id} 接口的请求。</para>
|
||||
/// </summary>
|
||||
public class GetMSEPayPaymentByOutPaymentIdRequest : WechatTenpayBusinessRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置平台支付单号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string OutPaymentId { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [GET] /mse-pay/payments/out-payment-id/{out_payment_id} 接口的响应。</para>
|
||||
/// </summary>
|
||||
public class GetMSEPayPaymentByOutPaymentIdResponse : GetMSEPayPaymentByPaymentIdResponse
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [GET] /mse-pay/payments/{payment_id} 接口的请求。</para>
|
||||
/// </summary>
|
||||
public class GetMSEPayPaymentByPaymentIdRequest : WechatTenpayBusinessRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置微企付支付单号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string PaymentId { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [GET] /mse-pay/payments/{payment_id} 接口的响应。</para>
|
||||
/// </summary>
|
||||
[WechatTenpayBusinessSensitive]
|
||||
public class GetMSEPayPaymentByPaymentIdResponse : WechatTenpayBusinessResponse
|
||||
{
|
||||
public static class Types
|
||||
{
|
||||
public class Payee
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置企业 ID。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("ent_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("ent_id")]
|
||||
public string EnterpriseId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置企业名称(需使用平台私钥解密)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("ent_name")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("ent_name")]
|
||||
[WechatTenpayBusinessSensitiveProperty]
|
||||
public string EnterpriseName { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置企业账户 ID。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("ent_acct_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("ent_acct_id")]
|
||||
public string EnterpriseAccountId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置银行卡号后 4 位。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("bank_account_number_last4")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("bank_account_number_last4")]
|
||||
public string? BankAccountNumberLast4String { get; set; }
|
||||
}
|
||||
|
||||
public class FailedReason
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置失败类型。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("failed_type")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("failed_type")]
|
||||
public string? FailedType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置失败原因详情。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("failed_detail")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("failed_detail")]
|
||||
public string? FailedDetail { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置平台支付单号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("out_payment_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("out_payment_id")]
|
||||
public string OutPaymentId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置微企付支付单号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("payment_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("payment_id")]
|
||||
public string PaymentId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置支付金额(单位:分)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("amount")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("amount")]
|
||||
public int Amount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置币种。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("currency")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("currency")]
|
||||
public string Currency { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置收款方信息。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("payee")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("payee")]
|
||||
public Types.Payee Payee { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置微企付用户 OpenId。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("user_openid")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("user_openid")]
|
||||
public string? UserOpenId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置订单状态。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("pay_status")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("pay_status")]
|
||||
public string Status { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置附言。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("memo")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("memo")]
|
||||
public string? Memo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置失败原因。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("failed_reason")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("failed_reason")]
|
||||
public Types.FailedReason? FailedReason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置附加信息。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("attachment")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("attachment")]
|
||||
public string? Attachment { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [POST] /mse-pay/product-applications/{request_no}/links 接口的请求。</para>
|
||||
/// </summary>
|
||||
public class CreateMSEPayProductApplicationLinkRequest : WechatTenpayBusinessRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置微企付开户申请单号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string RequestNumber { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [POST] /mse-pay/product-applications/{request_no}/links 接口的响应。</para>
|
||||
/// </summary>
|
||||
public class CreateMSEPayProductApplicationLinkResponse : WechatTenpayBusinessResponse
|
||||
{
|
||||
public static class Types
|
||||
{
|
||||
public class PCWebData
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置 URL。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("url")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("url")]
|
||||
public string Url { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置过期时间。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("expire_time")]
|
||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339DateTimeOffsetConverter))]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("expire_time")]
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339DateTimeOffsetConverter))]
|
||||
public DateTimeOffset ExpireTime { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置 PC Web 跳转链接信息。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("pc_web")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("pc_web")]
|
||||
public Types.PCWebData PCWebData { get; set; } = default!;
|
||||
}
|
||||
}
|
@ -0,0 +1,248 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [POST] /mse-pay/product-applications 接口的请求。</para>
|
||||
/// </summary>
|
||||
[WechatTenpayBusinessSensitive]
|
||||
public class CreateMSEPayProductApplicationRequest : WechatTenpayBusinessRequest
|
||||
{
|
||||
public static class Types
|
||||
{
|
||||
public class BusinessLicense
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置工商注册类型。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("business_register_type")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("business_register_type")]
|
||||
public string BusinessRegisterType { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置统一社会信用证代码。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("unified_social_credit_code")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("unified_social_credit_code")]
|
||||
public string UnifiedSocialCreditCode { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置商户名称。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("merchant_name")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("merchant_name")]
|
||||
public string MerchantName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置商户简称。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("merchant_short_name")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("merchant_short_name")]
|
||||
public string MerchantShortName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置法人姓名(需使用微企付公钥加密)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("legal_person_name")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("legal_person_name")]
|
||||
[WechatTenpayBusinessSensitiveProperty]
|
||||
public string LegalPersonName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置营业期限(格式:["yyyy-MM-dd", "yyyy-MM-dd"],长期用 "长期" 表示)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("validity_period")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("validity_period")]
|
||||
public IList<string> ValidityPeriodStrings { get; set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置营业执照照片 FileId。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("photocopy_file_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("photocopy_file_id")]
|
||||
public string PhotoCopyFileId { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class IdCard
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置身份证姓名(需使用微企付公钥加密)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("name")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("name")]
|
||||
[WechatTenpayBusinessSensitiveProperty]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置身份证号码(需使用微企付公钥加密)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("number")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("number")]
|
||||
[WechatTenpayBusinessSensitiveProperty]
|
||||
public string IdNumber { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置身份证期限(格式:["yyyy-MM-dd", "yyyy-MM-dd"],长期用 "长期" 表示)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("validity_period")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("validity_period")]
|
||||
public IList<string> ValidityPeriodStrings { get; set; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置身份证人像面照片 FileId。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("front_photocopy_file_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("front_photocopy_file_id")]
|
||||
public string FrontPhotoCopyFileId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置身份证国徽面照片 FileId。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("back_photocopy_file_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("back_photocopy_file_id")]
|
||||
public string BackPhotoCopyFileId { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class Contact
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置联系人手机号(需使用微企付公钥加密)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("mobile_number")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("mobile_number")]
|
||||
[WechatTenpayBusinessSensitiveProperty]
|
||||
public string MobileNumber { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class PayeeAccount
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置账户类型。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("account_type")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("account_type")]
|
||||
public string AccountType { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置开户名称(需使用微企付公钥加密)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("bank_account_name")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("bank_account_name")]
|
||||
[WechatTenpayBusinessSensitiveProperty]
|
||||
public string BankAccountName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置银行账号(需使用微企付公钥加密)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("bank_account_number")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("bank_account_number")]
|
||||
[WechatTenpayBusinessSensitiveProperty]
|
||||
public string BankAccountNumber { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置开户银行。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("bank_name")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("bank_name")]
|
||||
public string BankName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置开户银行联行号。与字段 <see cref="BankBranchName"/> 二选一。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("bank_branch_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("bank_branch_id")]
|
||||
public string? BankBranchId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置开户银行全称(含支行)。与字段 <see cref="BankBranchId"/> 二选一。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("bank_branch_name")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("bank_branch_name")]
|
||||
public string? BankBranchName { get; set; }
|
||||
}
|
||||
|
||||
public class Product
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置产品名称。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("product_name")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("product_name")]
|
||||
public string ProductName { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class NotifyConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置普通企业开户结果通知 URL。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("server_notify_url")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("server_notify_url")]
|
||||
public string? ServerNotifyUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置前端成功回调 URL。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("web_success_url")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("web_success_url")]
|
||||
public string? WebSuccessUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置前端异常回调刷新 URL。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("web_refresh_url")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("web_refresh_url")]
|
||||
public string? WebRefreshUrl { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置业务申请编号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("out_request_no")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("out_request_no")]
|
||||
public string OutRequestNumber { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置营业执照信息。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("business_license")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("business_license")]
|
||||
public Types.BusinessLicense? BusinessLicense { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置法人身份证信息。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("legal_person_id_card")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("legal_person_id_card")]
|
||||
public Types.IdCard? LegalPersonIdCard { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置联系人信息。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("contact_info")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("contact_info")]
|
||||
public Types.Contact Contact { get; set; } = new Types.Contact();
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置收款账户列表。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("payee_accounts")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("payee_accounts")]
|
||||
public IList<Types.PayeeAccount> PayeeAccountList { get; set; } = new List<Types.PayeeAccount>();
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置开通产品列表。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("products")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("products")]
|
||||
public IList<Types.Product> ProductList { get; set; } = new List<Types.Product>();
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置回调配置。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("notify_url")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("notify_url")]
|
||||
public Types.NotifyConfig NotifyConfig { get; set; } = new Types.NotifyConfig();
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [POST] /mse-pay/product-applications 接口的响应。</para>
|
||||
/// </summary>
|
||||
public class CreateMSEPayProductApplicationResponse : WechatTenpayBusinessResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置业务申请编号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("out_request_no")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("out_request_no")]
|
||||
public string OutRequestNumber { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置微企付开户申请单号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("request_no")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("request_no")]
|
||||
public string RequestNumber { get; set; } = default!;
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [GET] /mse-pay/product-applications/out-request-no/{out_request_no} 接口的请求。</para>
|
||||
/// </summary>
|
||||
public class GetMSEPayProductApplicationByOutRequestNumberRequest : WechatTenpayBusinessRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置业务申请编号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string OutRequestNumber { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [GET] /mse-pay/product-applications/out-request-no/{out_request_no} 接口的响应。</para>
|
||||
/// </summary>
|
||||
public class GetMSEPayProductApplicationByOutRequestNumberResponse : GetMSEPayProductApplicationByRequestNumberResponse
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [GET] /mse-pay/product-applications/{request_no} 接口的请求。</para>
|
||||
/// </summary>
|
||||
public class GetMSEPayProductApplicationByRequestNumberRequest : WechatTenpayBusinessRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置微企付开户申请单号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string RequestNumber { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [GET] /mse-pay/product-applications/{request_no} 接口的响应。</para>
|
||||
/// </summary>
|
||||
public class GetMSEPayProductApplicationByRequestNumberResponse : WechatTenpayBusinessResponse
|
||||
{
|
||||
public static class Types
|
||||
{
|
||||
public class Product
|
||||
{
|
||||
public static class Types
|
||||
{
|
||||
public class Account
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置企业账户 ID。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("ent_acct_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("ent_acct_id")]
|
||||
public string EnterpriseAccountId { get; set; } = default!;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置产品名称。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("product_name")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("product_name")]
|
||||
public string ProductName { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置开通状态。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("status")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("status")]
|
||||
public string Status { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置账户列表。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("accounts")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("accounts")]
|
||||
public Types.Account[]? AccountList { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置业务申请编号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("out_request_no")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("out_request_no")]
|
||||
public string OutRequestNumber { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置微企付开户申请单号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("request_no")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("request_no")]
|
||||
public string RequestNumber { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置企业 ID。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("ent_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("ent_id")]
|
||||
public string EnterpriseId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置开通状态。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("status")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("status")]
|
||||
public string Status { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置开通产品列表。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("product_details")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("product_details")]
|
||||
public Types.Product[] ProductList { get; set; } = default!;
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [POST] /mse-pay/redirects 接口的请求。</para>
|
||||
/// </summary>
|
||||
public class CreateMSEPayRedirectLinkRequest : WechatTenpayBusinessRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置微企付支付单号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("id")]
|
||||
public string PaymentId { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
using System;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>表示 [POST] /mse-pay/redirects 接口的响应。</para>
|
||||
/// </summary>
|
||||
public class CreateMSEPayRedirectLinkResponse : WechatTenpayBusinessResponse
|
||||
{
|
||||
public static class Types
|
||||
{
|
||||
public class PCWebData
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置 URL。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("url")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("url")]
|
||||
public string Url { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置过期时间。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("expire_time")]
|
||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339DateTimeOffsetConverter))]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("expire_time")]
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339DateTimeOffsetConverter))]
|
||||
public DateTimeOffset ExpireTime { get; set; }
|
||||
}
|
||||
|
||||
public class PCPluginData
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置 Key。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("key")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("key")]
|
||||
public string Key { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置过期时间。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("expire_time")]
|
||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339DateTimeOffsetConverter))]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("expire_time")]
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339DateTimeOffsetConverter))]
|
||||
public DateTimeOffset ExpireTime { get; set; }
|
||||
}
|
||||
|
||||
public class WxaQrcodeData
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置 URL。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("url")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("url")]
|
||||
public string Url { get; set; } = default!;
|
||||
}
|
||||
|
||||
public class MiniProgramData
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置小程序 AppId。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("mp_appid")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("mp_appid")]
|
||||
public string AppId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置小程序页面路径。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("mp_path")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("mp_path")]
|
||||
public string PagePath { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置小程序原始 ID。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("mp_username")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("mp_username")]
|
||||
public string Username { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置过期时间。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("expire_time")]
|
||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339DateTimeOffsetConverter))]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("expire_time")]
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339DateTimeOffsetConverter))]
|
||||
public DateTimeOffset ExpireTime { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置 PC Web 跳转链接信息。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("pc_web")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("pc_web")]
|
||||
public Types.PCWebData PCWebData { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置 PC 弹窗组件参数信息。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("pc_plugin")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("pc_plugin")]
|
||||
public Types.PCPluginData? PCPluginData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置小程序二维码参数信息。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("wx_qrcode")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("wx_qrcode")]
|
||||
public Types.WxaQrcodeData? WxaQrcodeData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置小程序参数信息。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("miniprogram")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("miniprogram")]
|
||||
public Types.MiniProgramData? MiniProgramData { get; set; }
|
||||
}
|
||||
}
|
26
src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/README.md
Normal file
26
src/SKIT.FlurlHttpClient.Wechat.TenpayBusiness/README.md
Normal file
@ -0,0 +1,26 @@
|
||||
## SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
|
||||
[](https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat) [](https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat) [](https://www.nuget.org/packages/SKIT.FlurlHttpClient.Wechat.TenpayBusiness) [](https://mit-license.org/)
|
||||
|
||||
基于 `Flurl.Http` 的腾讯微企付 API v3 版客户端。
|
||||
|
||||
---
|
||||
|
||||
### 【功能特性】
|
||||
|
||||
- 基于腾讯微企付 v3 版 API 封装。
|
||||
- 请求时自动生成签名,无需开发者手动干预。
|
||||
- 提供了腾讯微企付所需的 RSA、SHA-256、SM3、SM4 等算法工具类。
|
||||
- 提供了解析回调通知事件等扩展方法。
|
||||
|
||||
---
|
||||
|
||||
### 【开发文档】
|
||||
|
||||
[点此查看](https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat)。
|
||||
|
||||
---
|
||||
|
||||
### 【更新日志】
|
||||
|
||||
[点此查看](https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat/blob/main/CHANGELOG.md)。
|
@ -0,0 +1,48 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net461; net47; netstandard2.0; net5.0; net6.0</TargetFrameworks>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<NullableReferenceTypes>true</NullableReferenceTypes>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageId>SKIT.FlurlHttpClient.Wechat.TenpayBusinessV3</PackageId>
|
||||
<PackageIcon>LOGO.png</PackageIcon>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageProjectUrl>https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat</PackageProjectUrl>
|
||||
<PackageTags>Flurl.Http Tencent Tenpay 腾讯 微企付</PackageTags>
|
||||
<Version>0.0.1-alpha.1</Version>
|
||||
<Description>基于 Flurl.Http 的腾讯微企付 API v3 版客户端。</Description>
|
||||
<Authors>Fu Diwei</Authors>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat.git</RepositoryUrl>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
|
||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||
<Deterministic>true</Deterministic>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="../../LOGO.png" Pack="true" PackagePath="/" />
|
||||
<None Include="README.md" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Web" Condition="'$(TargetFramework)' == 'net461' Or '$(TargetFramework)' == 'net47'" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" Condition="'$(TargetFramework)' == 'net461'" />
|
||||
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
|
||||
<PackageReference Include="SKIT.FlurlHttpClient.Common" Version="2.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,61 @@
|
||||
using System;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Settings
|
||||
{
|
||||
public class Credentials
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化客户端时 <see cref="WechatTenpayBusinessClientOptions.PlatformId"/> 的副本。
|
||||
/// </summary>
|
||||
public string PlatformId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化客户端时 <see cref="WechatTenpayBusinessClientOptions.PlatformCertificateSerialNumber"/> 的副本。
|
||||
/// </summary>
|
||||
public string PlatformCertificateSerialNumber { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化客户端时 <see cref="WechatTenpayBusinessClientOptions.PlatformCertificatePrivateKey"/> 的副本。
|
||||
/// </summary>
|
||||
public string PlatformCertificatePrivateKey { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化客户端时 <see cref="WechatTenpayBusinessClientOptions.TBEPCertificateSerialNumber"/> 的副本。
|
||||
/// </summary>
|
||||
public string TBEPCertificateSerialNumber { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化客户端时 <see cref="WechatTenpayBusinessClientOptions.TBEPCertificatePublicKey"/> 的副本。
|
||||
/// </summary>
|
||||
public string TBEPCertificatePublicKey { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化客户端时 <see cref="WechatTenpayBusinessClientOptions.AutoEncryptRequestSensitivePropertyAlgorithm"/> 的副本。
|
||||
/// </summary>
|
||||
public string SensitivePropertyEncryptionAlgorithm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化客户端时 <see cref="WechatTenpayBusinessClientOptions.SensitivePropertyEncryptionSM4Key"/> 的副本。
|
||||
/// </summary>
|
||||
public string? SensitivePropertyEncryptionSM4Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化客户端时 <see cref="WechatTenpayBusinessClientOptions.SensitivePropertyEncryptionSM4IV"/> 的副本。
|
||||
/// </summary>
|
||||
public string? SensitivePropertyEncryptionSM4IV { get; set; }
|
||||
|
||||
internal Credentials(WechatTenpayBusinessClientOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
PlatformId = options.PlatformId;
|
||||
PlatformCertificateSerialNumber = options.PlatformCertificateSerialNumber;
|
||||
PlatformCertificatePrivateKey = options.PlatformCertificatePrivateKey;
|
||||
TBEPCertificateSerialNumber = options.TBEPCertificateSerialNumber;
|
||||
TBEPCertificatePublicKey = options.TBEPCertificatePublicKey;
|
||||
SensitivePropertyEncryptionAlgorithm = options.SensitivePropertyEncryptionAlgorithm;
|
||||
SensitivePropertyEncryptionSM4Key = options.SensitivePropertyEncryptionSM4Key;
|
||||
SensitivePropertyEncryptionSM4IV = options.SensitivePropertyEncryptionSM4IV;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
|
||||
{
|
||||
internal static class FileHttpContentBuilder
|
||||
{
|
||||
public static MultipartFormDataContent Build(string fileName, byte[] fileBytes, string fileContentType, string fileMetaJson, string formDataName = "file")
|
||||
{
|
||||
return Build(fileName: fileName, fileBytes: fileBytes, fileContentType: fileContentType, fileMetaJson: fileMetaJson, formDataName: formDataName, (_) => { }, (_) => { });
|
||||
}
|
||||
|
||||
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 (fileMetaJson == null) throw new ArgumentNullException(nameof(fileMetaJson));
|
||||
if (formDataName == null) throw new ArgumentNullException(nameof(formDataName));
|
||||
if (configureFileHttpContent == null) throw new ArgumentNullException(nameof(configureFileHttpContent));
|
||||
|
||||
fileBytes = fileBytes ?? Array.Empty<byte>();
|
||||
fileContentType = string.IsNullOrEmpty(fileContentType) ? "application/octet-stream" : fileContentType;
|
||||
formDataName = formDataName.Replace("\"", "");
|
||||
|
||||
ByteArrayContent metaContent = new ByteArrayContent(Encoding.UTF8.GetBytes(fileMetaJson));
|
||||
metaContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
|
||||
configureMetaHttpContent(metaContent);
|
||||
|
||||
ByteArrayContent fileContent = new ByteArrayContent(fileBytes);
|
||||
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse(fileContentType);
|
||||
configureFileHttpContent(fileContent);
|
||||
|
||||
string boundary = "--BOUNDARY--" + DateTimeOffset.Now.Ticks.ToString("x");
|
||||
MultipartFormDataContent httpContent = new MultipartFormDataContent(boundary);
|
||||
httpContent.Headers.ContentType = MediaTypeHeaderValue.Parse($"multipart/form-data; boundary={boundary}");
|
||||
httpContent.Add(metaContent, $"\"{Constants.FormDataFields.FORMDATA_META}\"");
|
||||
httpContent.Add(fileContent, $"\"{formDataName}\"", $"\"{HttpUtility.UrlEncode(fileName)}\"");
|
||||
return httpContent;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
using System.IO;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
|
||||
{
|
||||
internal static class FileNameToContentTypeMapper
|
||||
{
|
||||
public static string? GetContentTypeForImage(string fileName)
|
||||
{
|
||||
string extension = Path.GetExtension(fileName ?? "/")?.ToLower() ?? string.Empty;
|
||||
switch (extension)
|
||||
{
|
||||
case ".jpg":
|
||||
case ".jpeg":
|
||||
return "image/jpeg";
|
||||
case ".bmp":
|
||||
return "image/bmp";
|
||||
case ".png":
|
||||
return "image/png";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,191 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
|
||||
{
|
||||
internal static class ReflectionUtility
|
||||
{
|
||||
public delegate (bool Modified, string NewValue) ReplacePropertyStringValueReplacementHandler(object target, PropertyInfo currentProp, string oldValue);
|
||||
|
||||
public static void ReplacePropertyStringValue<T>(ref T obj, ReplacePropertyStringValueReplacementHandler replacement)
|
||||
{
|
||||
InnerReplacePropertyStringValue(ref obj, replacement);
|
||||
}
|
||||
|
||||
private static void InnerReplacePropertyStringValue<T>(ref T obj, ReplacePropertyStringValueReplacementHandler replacement)
|
||||
{
|
||||
if (obj == null) throw new ArgumentNullException(nameof(obj));
|
||||
if (replacement == null) throw new ArgumentNullException(nameof(replacement));
|
||||
|
||||
Type objType = obj.GetType();
|
||||
if (!objType.IsClass)
|
||||
throw new NotSupportedException();
|
||||
|
||||
if (objType.IsArray || obj is IList || obj is IDictionary)
|
||||
{
|
||||
InnerReplaceEachCollectionPropertyStringValue(ref obj, objType, replacement, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var childProp in objType.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||
{
|
||||
if (!childProp.CanWrite)
|
||||
continue;
|
||||
|
||||
Type propType = childProp.PropertyType;
|
||||
if (propType == typeof(string))
|
||||
{
|
||||
string value = (string)childProp.GetValue(obj, null)!;
|
||||
if (value is null)
|
||||
continue;
|
||||
|
||||
var result = replacement(obj, childProp, value);
|
||||
if (result.Modified)
|
||||
{
|
||||
childProp.SetValue(obj, result.NewValue);
|
||||
}
|
||||
}
|
||||
else if (propType.IsClass)
|
||||
{
|
||||
object? value = childProp.GetValue(obj, null);
|
||||
if (value is null)
|
||||
continue;
|
||||
|
||||
InnerReplacePropertyStringValue(ref value, replacement);
|
||||
childProp.SetValue(obj, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
object? value = childProp.GetValue(obj, null);
|
||||
if (value is null)
|
||||
continue;
|
||||
|
||||
InnerReplaceEachCollectionPropertyStringValue(ref value, propType, replacement, childProp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void InnerReplaceEachCollectionPropertyStringValue<T>(ref T obj, Type objType, ReplacePropertyStringValueReplacementHandler replacement, PropertyInfo? currentProp)
|
||||
{
|
||||
if (objType.IsArray)
|
||||
{
|
||||
var array = (obj as Array)!;
|
||||
|
||||
for (int i = 0, len = array.Length; i < len; i++)
|
||||
{
|
||||
object? element = array.GetValue(i);
|
||||
if (element is null)
|
||||
continue;
|
||||
|
||||
Type elementType = element.GetType();
|
||||
if (elementType == typeof(string))
|
||||
{
|
||||
if (currentProp == null)
|
||||
continue;
|
||||
if (!currentProp.CanWrite)
|
||||
continue;
|
||||
|
||||
var oldValue = (string)element!;
|
||||
var resHandler = replacement(obj!, currentProp, oldValue);
|
||||
if (resHandler.Modified && !array.IsReadOnly)
|
||||
{
|
||||
array.SetValue(resHandler.NewValue, i);
|
||||
}
|
||||
}
|
||||
else if (elementType.IsClass)
|
||||
{
|
||||
InnerReplacePropertyStringValue(ref element, replacement);
|
||||
//if (!array.IsReadOnly)
|
||||
//{
|
||||
// array.SetValue(element, i);
|
||||
//}
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (obj is IList)
|
||||
{
|
||||
var list = (obj as IList)!;
|
||||
|
||||
for (int i = 0, len = list.Count; i < len; i++)
|
||||
{
|
||||
object? element = list[i];
|
||||
if (element is null)
|
||||
continue;
|
||||
|
||||
Type elementType = element.GetType();
|
||||
if (elementType == typeof(string))
|
||||
{
|
||||
if (currentProp == null)
|
||||
continue;
|
||||
if (!currentProp.CanWrite)
|
||||
continue;
|
||||
|
||||
var oldValue = (string)element!;
|
||||
var resHandler = replacement(obj, currentProp, oldValue);
|
||||
if (resHandler.Modified && !list.IsReadOnly)
|
||||
{
|
||||
list[i] = resHandler.NewValue;
|
||||
}
|
||||
}
|
||||
else if (elementType.IsClass)
|
||||
{
|
||||
InnerReplacePropertyStringValue(ref element, replacement);
|
||||
//if (!list.IsReadOnly)
|
||||
//{
|
||||
// list[i] = element;
|
||||
//}
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (obj is IDictionary)
|
||||
{
|
||||
var dict = (obj as IDictionary)!;
|
||||
|
||||
foreach (DictionaryEntry entry in dict)
|
||||
{
|
||||
object? entryValue = entry.Value;
|
||||
if (entryValue is null)
|
||||
continue;
|
||||
|
||||
Type entryValueType = entryValue.GetType();
|
||||
if (entryValueType == typeof(string))
|
||||
{
|
||||
if (currentProp == null)
|
||||
continue;
|
||||
if (!currentProp.CanWrite)
|
||||
continue;
|
||||
|
||||
string oldValue = (string)entryValue!;
|
||||
var resHandler = replacement(obj, currentProp, oldValue);
|
||||
if (resHandler.Modified && !dict.IsReadOnly)
|
||||
{
|
||||
dict[entry.Key] = resHandler.NewValue;
|
||||
}
|
||||
}
|
||||
else if (entryValueType.IsClass)
|
||||
{
|
||||
InnerReplacePropertyStringValue(ref entryValue, replacement);
|
||||
//if (!dict.IsReadOnly)
|
||||
//{
|
||||
// dict[entry.Key] = entryValue;
|
||||
//}
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,206 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.OpenSsl;
|
||||
using Org.BouncyCastle.Security;
|
||||
using Org.BouncyCastle.X509;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// RSA 算法工具类。
|
||||
/// </summary>
|
||||
public static class RSAUtility
|
||||
{
|
||||
private const string RSA_CIPHER_ALGORITHM_ECB = "RSA/ECB";
|
||||
private const string RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1 = "OAEPWITHSHA1ANDMGF1PADDING";
|
||||
private const string RSA_SIGNER_ALGORITHM_SHA256 = "SHA-256withRSA";
|
||||
|
||||
/// <summary>
|
||||
/// 使用私钥基于 SHA-256 算法生成签名。
|
||||
/// </summary>
|
||||
/// <param name="privateKeyBytes">PKCS#8 私钥字节数组。</param>
|
||||
/// <param name="plainBytes">待签名的数据字节数组。</param>
|
||||
/// <returns>签名字节数组。</returns>
|
||||
public static byte[] SignWithSHA256(byte[] privateKeyBytes, byte[] plainBytes)
|
||||
{
|
||||
if (privateKeyBytes == null) throw new ArgumentNullException(nameof(privateKeyBytes));
|
||||
if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes));
|
||||
|
||||
RsaKeyParameters rsaKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes);
|
||||
return SignWithSHA256(rsaKeyParams, plainBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用私钥基于 SHA-256 算法生成签名。
|
||||
/// </summary>
|
||||
/// <param name="privateKey">PKCS#8 私钥(PEM 格式)。</param>
|
||||
/// <param name="plainText">待签名的文本数据。</param>
|
||||
/// <returns>经 Base64 编码的签名。</returns>
|
||||
public static string SignWithSHA256(string privateKey, string plainText)
|
||||
{
|
||||
if (privateKey == null) throw new ArgumentNullException(nameof(privateKey));
|
||||
if (plainText == null) throw new ArgumentNullException(nameof(plainText));
|
||||
|
||||
byte[] privateKeyBytes = ConvertPkcs8PrivateKeyToByteArray(privateKey);
|
||||
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
|
||||
byte[] signBytes = SignWithSHA256(privateKeyBytes, plainBytes);
|
||||
return Convert.ToBase64String(signBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用公钥基于 SHA-256 算法验证签名。
|
||||
/// </summary>
|
||||
/// <param name="publicKeyBytes">PKCS#8 公钥字节数据。</param>
|
||||
/// <param name="plainBytes">待验证的数据字节数据。</param>
|
||||
/// <param name="signBytes">待验证的签名字节数据。</param>
|
||||
/// <returns>验证结果。</returns>
|
||||
public static bool VerifyWithSHA256(byte[] publicKeyBytes, byte[] plainBytes, byte[] signBytes)
|
||||
{
|
||||
if (publicKeyBytes == null) throw new ArgumentNullException(nameof(publicKeyBytes));
|
||||
if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes));
|
||||
if (signBytes == null) throw new ArgumentNullException(nameof(signBytes));
|
||||
|
||||
RsaKeyParameters rsaKeyParams = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes);
|
||||
return VerifyWithSHA256(rsaKeyParams, plainBytes, signBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用公钥基于 SHA-256 算法验证签名。
|
||||
/// </summary>
|
||||
/// <param name="publicKey">PKCS#8 公钥(PEM 格式)。</param>
|
||||
/// <param name="plainText">待验证的文本数据。</param>
|
||||
/// <param name="signature">经 Base64 编码的待验证的签名。</param>
|
||||
/// <returns>验证结果。</returns>
|
||||
public static bool VerifyWithSHA256(string publicKey, string plainText, string signature)
|
||||
{
|
||||
if (publicKey == null) throw new ArgumentNullException(nameof(publicKey));
|
||||
if (plainText == null) throw new ArgumentNullException(nameof(plainText));
|
||||
if (signature == null) throw new ArgumentNullException(nameof(signature));
|
||||
|
||||
byte[] publicKeyBytes = ConvertPkcs8PublicKeyToByteArray(publicKey);
|
||||
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
|
||||
byte[] signBytes = Convert.FromBase64String(signature);
|
||||
return VerifyWithSHA256(publicKeyBytes, plainBytes, signBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用私钥基于 ECB 模式解密数据。
|
||||
/// </summary>
|
||||
/// <param name="privateKeyBytes">PKCS#8 私钥字节数据。</param>
|
||||
/// <param name="cipherBytes">待解密的数据字节数据。</param>
|
||||
/// <param name="paddingAlgorithm">填充算法。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/>)</param>
|
||||
/// <returns>解密后的数据字节数组。</returns>
|
||||
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 (cipherBytes == null) throw new ArgumentNullException(nameof(cipherBytes));
|
||||
|
||||
RsaKeyParameters rsaKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes);
|
||||
return DecryptWithECB(rsaKeyParams, cipherBytes, paddingAlgorithm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用私钥基于 ECB 模式解密数据。
|
||||
/// </summary>
|
||||
/// <param name="privateKey">PKCS#8 私钥(PEM 格式)。</param>
|
||||
/// <param name="cipherText">经 Base64 编码的待解密数据。</param>
|
||||
/// <param name="paddingAlgorithm">填充算法。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/>)</param>
|
||||
/// <returns>解密后的文本数据。</returns>
|
||||
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 (cipherText == null) throw new ArgumentNullException(nameof(cipherText));
|
||||
|
||||
byte[] privateKeyBytes = ConvertPkcs8PrivateKeyToByteArray(privateKey);
|
||||
byte[] cipherBytes = Convert.FromBase64String(cipherText);
|
||||
byte[] plainBytes = DecryptWithECB(privateKeyBytes, cipherBytes, paddingAlgorithm);
|
||||
return Encoding.UTF8.GetString(plainBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用公钥基于 ECB 模式加密数据。
|
||||
/// </summary>
|
||||
/// <param name="publicKeyBytes">PKCS#8 公钥字节数据。</param>
|
||||
/// <param name="plainBytes">待加密的数据字节数据。</param>
|
||||
/// <param name="paddingAlgorithm">填充算法。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/>)</param>
|
||||
/// <returns>加密后的数据字节数组。</returns>
|
||||
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 (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes));
|
||||
|
||||
RsaKeyParameters rsaKeyParams = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes);
|
||||
return EncryptWithECB(rsaKeyParams, plainBytes, paddingAlgorithm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用公钥基于 ECB 模式加密数据。
|
||||
/// </summary>
|
||||
/// <param name="publicKey">PKCS#8 公钥(PEM 格式)。</param>
|
||||
/// <param name="plainText">待加密的文本数据。</param>
|
||||
/// <param name="paddingAlgorithm">填充算法。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/>)</param>
|
||||
/// <returns>经 Base64 编码的加密数据。</returns>
|
||||
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 (plainText == null) throw new ArgumentNullException(nameof(plainText));
|
||||
|
||||
byte[] publicKeyBytes = ConvertPkcs8PublicKeyToByteArray(publicKey);
|
||||
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
|
||||
byte[] cipherBytes = EncryptWithECB(publicKeyBytes, plainBytes, paddingAlgorithm);
|
||||
return Convert.ToBase64String(cipherBytes);
|
||||
}
|
||||
|
||||
private static byte[] ConvertPkcs8PrivateKeyToByteArray(string privateKey)
|
||||
{
|
||||
privateKey = privateKey
|
||||
.Replace("-----BEGIN PRIVATE KEY-----", "")
|
||||
.Replace("-----END PRIVATE KEY-----", "");
|
||||
privateKey = Regex.Replace(privateKey, "\\s+", "");
|
||||
return Convert.FromBase64String(privateKey);
|
||||
}
|
||||
|
||||
private static byte[] ConvertPkcs8PublicKeyToByteArray(string publicKey)
|
||||
{
|
||||
publicKey = publicKey
|
||||
.Replace("-----BEGIN PUBLIC KEY-----", "")
|
||||
.Replace("-----END PUBLIC KEY-----", "");
|
||||
publicKey = Regex.Replace(publicKey, "\\s+", "");
|
||||
return Convert.FromBase64String(publicKey);
|
||||
}
|
||||
|
||||
private static byte[] SignWithSHA256(RsaKeyParameters rsaKeyParams, byte[] plainBytes)
|
||||
{
|
||||
ISigner signer = SignerUtilities.GetSigner(RSA_SIGNER_ALGORITHM_SHA256);
|
||||
signer.Init(true, rsaKeyParams);
|
||||
signer.BlockUpdate(plainBytes, 0, plainBytes.Length);
|
||||
return signer.GenerateSignature();
|
||||
}
|
||||
|
||||
private static bool VerifyWithSHA256(RsaKeyParameters rsaKeyParams, byte[] plainBytes, byte[] signBytes)
|
||||
{
|
||||
ISigner signer = SignerUtilities.GetSigner(RSA_SIGNER_ALGORITHM_SHA256);
|
||||
signer.Init(false, rsaKeyParams);
|
||||
signer.BlockUpdate(plainBytes, 0, plainBytes.Length);
|
||||
return signer.VerifySignature(signBytes);
|
||||
}
|
||||
|
||||
private static byte[] EncryptWithECB(RsaKeyParameters rsaKeyParams, byte[] plainBytes, string paddingAlgorithm)
|
||||
{
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher($"{RSA_CIPHER_ALGORITHM_ECB}/{paddingAlgorithm}");
|
||||
cipher.Init(true, rsaKeyParams);
|
||||
return cipher.DoFinal(plainBytes);
|
||||
}
|
||||
|
||||
private static byte[] DecryptWithECB(RsaKeyParameters rsaKeyParams, byte[] cipherBytes, string paddingAlgorithm)
|
||||
{
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher($"{RSA_CIPHER_ALGORITHM_ECB}/{paddingAlgorithm}");
|
||||
cipher.Init(false, rsaKeyParams);
|
||||
return cipher.DoFinal(cipherBytes);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// SHA-256 算法工具类。
|
||||
/// </summary>
|
||||
public static class SHA256Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取 SHA-256 信息摘要。
|
||||
/// </summary>
|
||||
/// <param name="bytes">信息字节数组。</param>
|
||||
/// <returns>信息摘要字节数组。</returns>
|
||||
public static byte[] Hash(byte[] bytes)
|
||||
{
|
||||
if (bytes == null) throw new ArgumentNullException(nameof(bytes));
|
||||
|
||||
using SHA256 sha = SHA256.Create();
|
||||
return sha.ComputeHash(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 SHA-256 信息摘要。
|
||||
/// </summary>
|
||||
/// <param name="message">文本信息。</param>
|
||||
/// <returns>信息摘要。</returns>
|
||||
public static string Hash(string message)
|
||||
{
|
||||
if (message == null) throw new ArgumentNullException(nameof(message));
|
||||
|
||||
byte[] msgBytes = Encoding.UTF8.GetBytes(message);
|
||||
byte[] hashBytes = Hash(msgBytes);
|
||||
return BitConverter.ToString(hashBytes).Replace("-", "");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,370 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// SM3 算法工具类。
|
||||
/// </summary>
|
||||
public static class SM3Utility
|
||||
{
|
||||
internal static class BitOperator
|
||||
{
|
||||
public static int URShift(int number, int bits)
|
||||
{
|
||||
if (number >= 0)
|
||||
return number >> bits;
|
||||
else
|
||||
return (number >> bits) + (2 << ~bits);
|
||||
}
|
||||
|
||||
public static int URShift(int number, long bits)
|
||||
{
|
||||
return URShift(number, (int)bits);
|
||||
}
|
||||
|
||||
public static long URShift(long number, int bits)
|
||||
{
|
||||
if (number >= 0)
|
||||
return number >> bits;
|
||||
else
|
||||
return (number >> bits) + (2L << ~bits);
|
||||
}
|
||||
|
||||
public static long URShift(long number, long bits)
|
||||
{
|
||||
return URShift(number, (int)bits);
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class GeneralDigest : IDigest
|
||||
{
|
||||
private const int ByteLength = 64;
|
||||
private readonly byte[] XBuf;
|
||||
private int XBufOff;
|
||||
private long ByteCount;
|
||||
|
||||
public abstract string AlgorithmName { get; }
|
||||
|
||||
protected GeneralDigest()
|
||||
{
|
||||
XBuf = new byte[4];
|
||||
}
|
||||
|
||||
protected GeneralDigest(GeneralDigest t)
|
||||
{
|
||||
XBuf = new byte[t.XBuf.Length];
|
||||
Array.Copy(t.XBuf, 0, XBuf, 0, t.XBuf.Length);
|
||||
|
||||
XBufOff = t.XBufOff;
|
||||
ByteCount = t.ByteCount;
|
||||
}
|
||||
|
||||
public void Update(byte input)
|
||||
{
|
||||
XBuf[XBufOff++] = input;
|
||||
|
||||
if (XBufOff == XBuf.Length)
|
||||
{
|
||||
ProcessWord(XBuf, 0);
|
||||
XBufOff = 0;
|
||||
}
|
||||
|
||||
ByteCount++;
|
||||
}
|
||||
|
||||
public void BlockUpdate(byte[] input, int inOff, int length)
|
||||
{
|
||||
//更新当前消息摘要
|
||||
while ((XBufOff != 0) && (length > 0))
|
||||
{
|
||||
Update(input[inOff]);
|
||||
inOff++;
|
||||
length--;
|
||||
}
|
||||
|
||||
//处理完整的消息摘要
|
||||
while (length > XBuf.Length)
|
||||
{
|
||||
ProcessWord(input, inOff);
|
||||
|
||||
inOff += XBuf.Length;
|
||||
length -= XBuf.Length;
|
||||
ByteCount += XBuf.Length;
|
||||
}
|
||||
|
||||
//填充剩余的消息摘要
|
||||
while (length > 0)
|
||||
{
|
||||
Update(input[inOff]);
|
||||
|
||||
inOff++;
|
||||
length--;
|
||||
}
|
||||
}
|
||||
|
||||
public void Finish()
|
||||
{
|
||||
long bitLength = (ByteCount << 3);
|
||||
|
||||
//添加字节
|
||||
Update(unchecked((byte)128));
|
||||
|
||||
while (XBufOff != 0) Update(unchecked((byte)0));
|
||||
ProcessLength(bitLength);
|
||||
ProcessBlock();
|
||||
}
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
ByteCount = 0;
|
||||
XBufOff = 0;
|
||||
Array.Clear(XBuf, 0, XBuf.Length);
|
||||
}
|
||||
|
||||
public int GetByteLength()
|
||||
{
|
||||
return ByteLength;
|
||||
}
|
||||
|
||||
internal abstract void ProcessWord(byte[] input, int inOff);
|
||||
|
||||
internal abstract void ProcessLength(long bitLength);
|
||||
|
||||
internal abstract void ProcessBlock();
|
||||
|
||||
public abstract int GetDigestSize();
|
||||
|
||||
public abstract int DoFinal(byte[] output, int outOff);
|
||||
}
|
||||
|
||||
internal sealed class SM3Digest : GeneralDigest
|
||||
{
|
||||
private const int DIGEST_LENGTH = 32;
|
||||
private const int T_1 = 0x79cc4519;
|
||||
private const int T_2 = 0x7a879d8a;
|
||||
|
||||
private static readonly int[] IV = new int[] { 0x7380166f, 0x4914b2b9, 0x172442d7, unchecked((int)0xda8a0600), unchecked((int)0xa96f30bc), 0x163138aa, unchecked((int)0xe38dee4d), unchecked((int)0xb0fb0e4e) };
|
||||
private static readonly int[] X0 = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
|
||||
private readonly int[] _v1 = new int[8];
|
||||
private readonly int[] _v2 = new int[8];
|
||||
private readonly int[] _x = new int[68];
|
||||
private int _xOff;
|
||||
|
||||
public override string AlgorithmName { get { return "SM3"; } }
|
||||
|
||||
public SM3Digest()
|
||||
: base()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
public SM3Digest(SM3Digest t)
|
||||
: base(t)
|
||||
{
|
||||
Array.Copy(t._x, 0, _x, 0, t._x.Length);
|
||||
_xOff = t._xOff;
|
||||
Array.Copy(t._v1, 0, _v1, 0, t._v1.Length);
|
||||
}
|
||||
|
||||
private static void ConvertIntegerToBigEndian(int num, byte[] bytes, int offset)
|
||||
{
|
||||
bytes[offset] = (byte)(BitOperator.URShift(num, 24));
|
||||
bytes[++offset] = (byte)(BitOperator.URShift(num, 16));
|
||||
bytes[++offset] = (byte)(BitOperator.URShift(num, 8));
|
||||
bytes[++offset] = (byte)(num);
|
||||
}
|
||||
|
||||
private static int Rotate(int x, int n)
|
||||
{
|
||||
return (x << n) | (BitOperator.URShift(x, (32 - n)));
|
||||
}
|
||||
|
||||
private static int P0(int x)
|
||||
{
|
||||
return (x) ^ Rotate(x, 9) ^ Rotate(x, 17);
|
||||
}
|
||||
|
||||
private static int P1(int x)
|
||||
{
|
||||
return (x) ^ Rotate(x, 15) ^ Rotate(x, 23);
|
||||
}
|
||||
|
||||
private static int FFOne(int x, int y, int z)
|
||||
{
|
||||
return (x ^ y ^ z);
|
||||
}
|
||||
|
||||
private static int FFSecond(int x, int y, int z)
|
||||
{
|
||||
return ((x & y) | (x & z) | (y & z));
|
||||
}
|
||||
|
||||
private static int GGOne(int x, int y, int z)
|
||||
{
|
||||
return (x ^ y ^ z);
|
||||
}
|
||||
|
||||
private static int GGSecond(int x, int y, int z)
|
||||
{
|
||||
return ((x & y) | (~x & z));
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
|
||||
Array.Copy(IV, 0, _v1, 0, IV.Length);
|
||||
|
||||
_xOff = 0;
|
||||
Array.Copy(X0, 0, _x, 0, X0.Length);
|
||||
}
|
||||
|
||||
internal override void ProcessWord(byte[] input, int inOff)
|
||||
{
|
||||
int n = input[inOff] << 24;
|
||||
n |= (input[++inOff] & 0xff) << 16;
|
||||
n |= (input[++inOff] & 0xff) << 8;
|
||||
n |= (input[++inOff] & 0xff);
|
||||
_x[_xOff] = n;
|
||||
|
||||
if (++_xOff == 16)
|
||||
{
|
||||
ProcessBlock();
|
||||
}
|
||||
}
|
||||
|
||||
internal override void ProcessLength(long bitLength)
|
||||
{
|
||||
if (_xOff > 14)
|
||||
{
|
||||
ProcessBlock();
|
||||
}
|
||||
|
||||
_x[14] = (int)(BitOperator.URShift(bitLength, 32));
|
||||
_x[15] = (int)(bitLength & unchecked((int)0xffffffff));
|
||||
}
|
||||
|
||||
internal override void ProcessBlock()
|
||||
{
|
||||
int j;
|
||||
|
||||
int[] ww = _x;
|
||||
int[] ww_ = new int[64];
|
||||
|
||||
for (j = 16; j < 68; j++)
|
||||
{
|
||||
ww[j] = P1(ww[j - 16] ^ ww[j - 9] ^ (Rotate(ww[j - 3], 15))) ^ (Rotate(ww[j - 13], 7)) ^ ww[j - 6];
|
||||
}
|
||||
for (j = 0; j < 64; j++)
|
||||
{
|
||||
ww_[j] = ww[j] ^ ww[j + 4];
|
||||
}
|
||||
|
||||
int[] vv = _v1;
|
||||
int[] vv_ = _v2;
|
||||
|
||||
Array.Copy(vv, 0, vv_, 0, IV.Length);
|
||||
|
||||
int SS1, SS2, TT1, TT2;
|
||||
int aaa;
|
||||
|
||||
for (j = 0; j < 16; j++)
|
||||
{
|
||||
aaa = Rotate(vv_[0], 12);
|
||||
SS1 = aaa + vv_[4] + Rotate(T_1, j);
|
||||
SS1 = Rotate(SS1, 7);
|
||||
SS2 = SS1 ^ aaa;
|
||||
|
||||
TT1 = FFOne(vv_[0], vv_[1], vv_[2]) + vv_[3] + SS2 + ww_[j];
|
||||
TT2 = GGOne(vv_[4], vv_[5], vv_[6]) + vv_[7] + SS1 + ww[j];
|
||||
|
||||
vv_[3] = vv_[2];
|
||||
vv_[2] = Rotate(vv_[1], 9);
|
||||
vv_[1] = vv_[0];
|
||||
vv_[0] = TT1;
|
||||
vv_[7] = vv_[6];
|
||||
vv_[6] = Rotate(vv_[5], 19);
|
||||
vv_[5] = vv_[4];
|
||||
vv_[4] = P0(TT2);
|
||||
}
|
||||
|
||||
for (j = 16; j < 64; j++)
|
||||
{
|
||||
aaa = Rotate(vv_[0], 12);
|
||||
SS1 = aaa + vv_[4] + Rotate(T_2, j);
|
||||
SS1 = Rotate(SS1, 7);
|
||||
SS2 = SS1 ^ aaa;
|
||||
|
||||
TT1 = FFSecond(vv_[0], vv_[1], vv_[2]) + vv_[3] + SS2 + ww_[j];
|
||||
TT2 = GGSecond(vv_[4], vv_[5], vv_[6]) + vv_[7] + SS1 + ww[j];
|
||||
|
||||
vv_[3] = vv_[2];
|
||||
vv_[2] = Rotate(vv_[1], 9);
|
||||
vv_[1] = vv_[0];
|
||||
vv_[0] = TT1;
|
||||
vv_[7] = vv_[6];
|
||||
vv_[6] = Rotate(vv_[5], 19);
|
||||
vv_[5] = vv_[4];
|
||||
vv_[4] = P0(TT2);
|
||||
}
|
||||
|
||||
for (j = 0; j < 8; j++)
|
||||
{
|
||||
vv[j] ^= vv_[j];
|
||||
}
|
||||
|
||||
_xOff = 0;
|
||||
Array.Copy(X0, 0, _x, 0, X0.Length);
|
||||
}
|
||||
|
||||
public override int GetDigestSize()
|
||||
{
|
||||
return DIGEST_LENGTH;
|
||||
}
|
||||
|
||||
public override int DoFinal(byte[] output, int outOff)
|
||||
{
|
||||
Finish();
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
ConvertIntegerToBigEndian(_v1[i], output, outOff + i * 4);
|
||||
}
|
||||
Reset();
|
||||
return DIGEST_LENGTH;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 SM3 哈希值。
|
||||
/// </summary>
|
||||
/// <param name="bytes">信息字节数组。</param>
|
||||
/// <returns>哈希字节数组。</returns>
|
||||
public static byte[] Hash(byte[] bytes)
|
||||
{
|
||||
if (bytes == null) throw new ArgumentNullException(nameof(bytes));
|
||||
|
||||
SM3Digest sm3 = new SM3Digest();
|
||||
sm3.BlockUpdate(bytes, 0, bytes.Length);
|
||||
byte[] hashBytes = new byte[sm3.GetDigestSize()];
|
||||
sm3.DoFinal(hashBytes, 0);
|
||||
return hashBytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 SM3 哈希值。
|
||||
/// </summary>
|
||||
/// <param name="message">文本信息。</param>
|
||||
/// <returns>哈希值。</returns>
|
||||
public static string Hash(string message)
|
||||
{
|
||||
if (message == null) throw new ArgumentNullException(nameof(message));
|
||||
|
||||
byte[] msgBytes = Encoding.UTF8.GetBytes(message);
|
||||
byte[] hashBytes = Hash(msgBytes);
|
||||
return BitConverter.ToString(hashBytes).Replace("-", "");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.Security;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// SM4 算法工具类。
|
||||
/// </summary>
|
||||
public static class SM4Utility
|
||||
{
|
||||
// REF: https://github.com/bcgit/bc-csharp/blob/master/crypto/src/security/CipherUtilities.cs
|
||||
private const string SM4_ALG_NAME = "SM4";
|
||||
private const string SM4_CIPHER_ALGORITHM_CBC = "SM4/CBC";
|
||||
private const string SM4_CIPHER_PADDING_PKCS7PADDING = "PKCS7PADDING";
|
||||
|
||||
/// <summary>
|
||||
/// 基于 CBC 模式解密数据。
|
||||
/// </summary>
|
||||
/// <param name="keyBytes">密钥字节数据。</param>
|
||||
/// <param name="ivBytes">偏移量字节数据。</param>
|
||||
/// <param name="cipherBytes">待解密的数据字节数据。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="SM4_CIPHER_PADDING_PKCS7PADDING"/>)</param>
|
||||
/// <returns>解密后的数据字节数组。</returns>
|
||||
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 (cipherBytes == null) throw new ArgumentNullException(nameof(cipherBytes));
|
||||
|
||||
KeyParameter sm4KeyParams = ParameterUtilities.CreateKeyParameter(SM4_ALG_NAME, keyBytes);
|
||||
ParametersWithIV sm4keyParamsWithIv = new ParametersWithIV(sm4KeyParams, ivBytes);
|
||||
return DecryptWithCBC(sm4keyParamsWithIv, cipherBytes, paddingMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于 CBC 模式解密数据。
|
||||
/// </summary>
|
||||
/// <param name="key">经 Base64 编码的密钥。</param>
|
||||
/// <param name="iv">>经 Base64 编码的偏移量。</param>
|
||||
/// <param name="cipherText">经 Base64 编码的待解密数据。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="SM4_CIPHER_PADDING_PKCS7PADDING"/>)</param>
|
||||
/// <returns>解密后的文本数据。</returns>
|
||||
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 (cipherText == null) throw new ArgumentNullException(nameof(cipherText));
|
||||
|
||||
byte[] keyBytes = Convert.FromBase64String(key);
|
||||
byte[] ivBytes = Convert.FromBase64String(iv);
|
||||
byte[] cipherBytes = Convert.FromBase64String(cipherText);
|
||||
byte[] plainBytes = DecryptWithCBC(keyBytes, ivBytes, cipherBytes, paddingMode);
|
||||
return Encoding.UTF8.GetString(plainBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于 CBC 模式加密数据。
|
||||
/// </summary>
|
||||
/// <param name="keyBytes">密钥字节数组。</param>
|
||||
/// <param name="ivBytes">偏移量字节数据。</param>
|
||||
/// <param name="plainBytes">待加密的数据字节数据。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="SM4_CIPHER_PADDING_PKCS7PADDING"/>)</param>
|
||||
/// <returns>加密后的数据字节数组。</returns>
|
||||
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 (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes));
|
||||
|
||||
KeyParameter sm4KeyParams = ParameterUtilities.CreateKeyParameter(SM4_ALG_NAME, keyBytes);
|
||||
ParametersWithIV sm4keyParamsWithIv = new ParametersWithIV(sm4KeyParams, ivBytes);
|
||||
return EncryptWithCBC(sm4keyParamsWithIv, plainBytes, paddingMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于 CBC 模式加密数据。
|
||||
/// </summary>
|
||||
/// <param name="key">经 Base64 编码的密钥。</param>
|
||||
/// <param name="iv">>经 Base64 编码的偏移量。</param>
|
||||
/// <param name="plainText">待加密的文本数据。</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="SM4_CIPHER_PADDING_PKCS7PADDING"/>)</param>
|
||||
/// <returns>经 Base64 编码的加密数据。</returns>
|
||||
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 (plainText == null) throw new ArgumentNullException(nameof(plainText));
|
||||
|
||||
byte[] keyBytes = Convert.FromBase64String(key);
|
||||
byte[] ivBytes = Convert.FromBase64String(iv);
|
||||
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
|
||||
byte[] cipherBytes = EncryptWithCBC(keyBytes, ivBytes, plainBytes, paddingMode);
|
||||
return Convert.ToBase64String(cipherBytes);
|
||||
}
|
||||
|
||||
private static byte[] EncryptWithCBC(ICipherParameters sm4KeyParams, byte[] plainBytes, string paddingMode)
|
||||
{
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher($"{SM4_CIPHER_ALGORITHM_CBC}/{paddingMode}");
|
||||
cipher.Init(true, sm4KeyParams);
|
||||
return cipher.DoFinal(plainBytes);
|
||||
}
|
||||
|
||||
private static byte[] DecryptWithCBC(ICipherParameters sm4KeyParams, byte[] cipherBytes, string paddingMode)
|
||||
{
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher($"{SM4_CIPHER_ALGORITHM_CBC}/{paddingMode}");
|
||||
cipher.Init(false, sm4KeyParams);
|
||||
return cipher.DoFinal(cipherBytes);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Flurl.Http;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
/// <summary>
|
||||
/// 一个腾讯微企付 API HTTP 客户端。
|
||||
/// </summary>
|
||||
public class WechatTenpayBusinessClient : CommonClientBase, ICommonClient
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前客户端使用的腾讯微企付平台凭证。
|
||||
/// </summary>
|
||||
public Settings.Credentials Credentials { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否自动加密请求中的敏感信息字段。
|
||||
/// </summary>
|
||||
protected bool AutoEncryptRequestSensitiveProperty { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否自动解密请求中的敏感信息字段。
|
||||
/// </summary>
|
||||
protected bool AutoDecryptResponseSensitiveProperty { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 用指定的配置项初始化 <see cref="WechatTenpayBusinessClient"/> 类的新实例。
|
||||
/// </summary>
|
||||
/// <param name="options">配置项。</param>
|
||||
public WechatTenpayBusinessClient(WechatTenpayBusinessClientOptions options)
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException(nameof(options));
|
||||
|
||||
Credentials = new Settings.Credentials(options);
|
||||
AutoEncryptRequestSensitiveProperty = options.AutoEncryptRequestSensitiveProperty;
|
||||
AutoDecryptResponseSensitiveProperty = options.AutoDecryptResponseSensitiveProperty;
|
||||
|
||||
FlurlClient.BaseUrl = options.Endpoints ?? WechatTenpayBusinessEndpoints.DEFAULT;
|
||||
FlurlClient.Headers.Remove(FlurlHttpClient.Constants.HttpHeaders.Accept);
|
||||
FlurlClient.Headers.Remove(FlurlHttpClient.Constants.HttpHeaders.AcceptLanguage);
|
||||
FlurlClient.WithHeader(FlurlHttpClient.Constants.HttpHeaders.Accept, "application/json");
|
||||
FlurlClient.WithTimeout(TimeSpan.FromMilliseconds(options.Timeout));
|
||||
|
||||
Interceptors.Add(new Interceptors.WechatTenpayBusinessRequestSignatureInterceptor(
|
||||
signAlg: options.SignAlgorithm,
|
||||
platformId: options.PlatformId,
|
||||
platformCertSn: options.PlatformCertificateSerialNumber,
|
||||
platformCertPk: options.PlatformCertificatePrivateKey
|
||||
));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用当前客户端生成一个新的 <see cref="IFlurlRequest"/> 对象。
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="method"></param>
|
||||
/// <param name="urlSegments"></param>
|
||||
/// <returns></returns>
|
||||
public IFlurlRequest CreateRequest(WechatTenpayBusinessRequest request, HttpMethod method, params object[] urlSegments)
|
||||
{
|
||||
IFlurlRequest flurlRequest = FlurlClient.Request(urlSegments).WithVerb(method);
|
||||
|
||||
if (AutoEncryptRequestSensitiveProperty)
|
||||
{
|
||||
this.EncryptRequestSensitiveProperty(request);
|
||||
}
|
||||
|
||||
if (request.Timeout != null)
|
||||
{
|
||||
flurlRequest.WithTimeout(TimeSpan.FromMilliseconds(request.Timeout.Value));
|
||||
}
|
||||
|
||||
if (request.TBEPEncryption != null)
|
||||
{
|
||||
flurlRequest.Headers.Remove("TBEP-Encrypt");
|
||||
flurlRequest.WithHeader("TBEP-Encrypt", $"enc_key=\"{request.TBEPEncryption.EncryptedKey}\",iv=\"{Convert.ToBase64String(Encoding.UTF8.GetBytes(request.TBEPEncryption.IV))}\",tbep_serial_number=\"{request.TBEPEncryption.CertificateSerialNumber}\",algorithm=\"{request.TBEPEncryption.Algorithm}\"");
|
||||
}
|
||||
|
||||
return flurlRequest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步发起请求。
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="flurlRequest"></param>
|
||||
/// <param name="httpContent"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<T> SendRequestAsync<T>(IFlurlRequest flurlRequest, HttpContent? httpContent = null, CancellationToken cancellationToken = default)
|
||||
where T : WechatTenpayBusinessResponse, new()
|
||||
{
|
||||
if (flurlRequest == null) throw new ArgumentNullException(nameof(flurlRequest));
|
||||
|
||||
if (httpContent != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(httpContent.Headers.ContentType?.MediaType))
|
||||
httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using IFlurlResponse flurlResponse = await base.SendRequestAsync(flurlRequest, httpContent, cancellationToken);
|
||||
return await WrapResponseWithJsonAsync<T>(flurlResponse, cancellationToken);
|
||||
}
|
||||
catch (FlurlHttpException ex)
|
||||
{
|
||||
throw new WechatTenpayBusinessException(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步发起请求。
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="flurlRequest"></param>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<T> SendRequestWithJsonAsync<T>(IFlurlRequest flurlRequest, object? data = null, CancellationToken cancellationToken = default)
|
||||
where T : WechatTenpayBusinessResponse, new()
|
||||
{
|
||||
if (flurlRequest == null) throw new ArgumentNullException(nameof(flurlRequest));
|
||||
|
||||
try
|
||||
{
|
||||
bool isSimpleRequest = data == null ||
|
||||
flurlRequest.Verb == HttpMethod.Get ||
|
||||
flurlRequest.Verb == HttpMethod.Head ||
|
||||
flurlRequest.Verb == HttpMethod.Options;
|
||||
using IFlurlResponse flurlResponse = isSimpleRequest ?
|
||||
await base.SendRequestAsync(flurlRequest, null, cancellationToken) :
|
||||
await base.SendRequestWithJsonAsync(flurlRequest, data, cancellationToken);
|
||||
return await WrapResponseWithJsonAsync<T>(flurlResponse, cancellationToken);
|
||||
}
|
||||
catch (FlurlHttpException ex)
|
||||
{
|
||||
throw new WechatTenpayBusinessException(ex.Message, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private new async Task<TResponse> WrapResponseWithJsonAsync<TResponse>(IFlurlResponse flurlResponse, CancellationToken cancellationToken = default)
|
||||
where TResponse : WechatTenpayBusinessResponse, new()
|
||||
{
|
||||
TResponse result = await base.WrapResponseWithJsonAsync<TResponse>(flurlResponse, cancellationToken);
|
||||
|
||||
string? strTBEPEncryption = flurlResponse.Headers.GetAll("TBEP-Encrypt").FirstOrDefault();
|
||||
if (!string.IsNullOrEmpty(strTBEPEncryption))
|
||||
{
|
||||
IDictionary<string, string?> dictTBEPEncryption = strTBEPEncryption
|
||||
.Split(',')
|
||||
.Select(s => s.Trim().Split(new char[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries))
|
||||
.ToDictionary(
|
||||
k => k[0],
|
||||
v => v.Length > 1 ? v[1].TrimStart('\"').TrimEnd('\"') : null
|
||||
);
|
||||
result.TBEPEncryption = new WechatTenpayBusinessResponseTBEPEncryption();
|
||||
result.TBEPEncryption.PlatformId = dictTBEPEncryption["platform_id"];
|
||||
result.TBEPEncryption.EncryptedKey = dictTBEPEncryption["enc_key"];
|
||||
result.TBEPEncryption.IV = dictTBEPEncryption["iv"];
|
||||
result.TBEPEncryption.CertificateSerialNumber = dictTBEPEncryption["platform_serial_number"];
|
||||
result.TBEPEncryption.Algorithm = dictTBEPEncryption["algorithm"];
|
||||
|
||||
if (AutoDecryptResponseSensitiveProperty && result.IsSuccessful())
|
||||
{
|
||||
this.DecryptResponseSensitiveProperty(result);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
using System;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
/// <summary>
|
||||
/// 一个用于构造 <see cref="WechatTenpayBusinessClient"/> 时使用的配置项。
|
||||
/// </summary>
|
||||
public class WechatTenpayBusinessClientOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置请求超时时间(单位:毫秒)。
|
||||
/// <para>默认值:30000</para>
|
||||
/// </summary>
|
||||
public int Timeout { get; set; } = 30 * 1000;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置腾讯微企付 API 域名。
|
||||
/// <para>默认值:<see cref="WechatTenpayBusinessEndpoints.DEFAULT"/></para>
|
||||
/// </summary>
|
||||
public string Endpoints { get; set; } = WechatTenpayBusinessEndpoints.DEFAULT;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置腾讯微企付 API 签名认证方式。
|
||||
/// <para>默认值:<see cref="Constants.SignAlgorithms.SHA245_WITH_RSA"/></para>
|
||||
/// </summary>
|
||||
public string SignAlgorithm { get; set; } = Constants.SignAlgorithms.SHA245_WITH_RSA;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置腾讯微企付平台账号。
|
||||
/// </summary>
|
||||
public string PlatformId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置腾讯微企付平台 API 证书序列号。
|
||||
/// </summary>
|
||||
public string PlatformCertificateSerialNumber { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置腾讯微企付平台 API 证书私钥。
|
||||
/// </summary>
|
||||
public string PlatformCertificatePrivateKey { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置腾讯微企付证书序列号。
|
||||
/// </summary>
|
||||
public string TBEPCertificateSerialNumber { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置腾讯微企付证书公钥。
|
||||
/// </summary>
|
||||
public string TBEPCertificatePublicKey { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置是否自动加密请求中的敏感字段数据。
|
||||
/// <para>注意:启用该功能需配合 <see cref="SensitivePropertyEncryptionSM4Key"/> 和 <see cref="SensitivePropertyEncryptionSM4IV"/> 使用。</para>
|
||||
/// </summary>
|
||||
public bool AutoEncryptRequestSensitiveProperty { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置自动加密请求重敏感字段数据时使用的算法。
|
||||
/// <para>默认值:<see cref="Constants.EncryptionAlgorithms.RSA_OAEP_WITH_SM4_128_CBC"/></para>
|
||||
/// </summary>
|
||||
public string SensitivePropertyEncryptionAlgorithm { get; set; } = Constants.EncryptionAlgorithms.RSA_OAEP_WITH_SM4_128_CBC;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置自动加密请求中的敏感字段数据时 SM4 算法所需密钥。
|
||||
/// </summary>
|
||||
public string? SensitivePropertyEncryptionSM4Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置自动加密请求中的敏感字段数据时 SM4 算法所需偏移量。
|
||||
/// </summary>
|
||||
public string? SensitivePropertyEncryptionSM4IV { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置是否自动解密响应中的敏感字段数据。
|
||||
/// </summary>
|
||||
public bool AutoDecryptResponseSensitiveProperty { get; set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
/// <summary>
|
||||
/// 腾讯微企付 API 接口域名。
|
||||
/// </summary>
|
||||
public static class WechatTenpayBusinessEndpoints
|
||||
{
|
||||
/// <summary>
|
||||
/// 主域名(默认)。
|
||||
/// </summary>
|
||||
public const string DEFAULT = "https://api-business.tenpay.com/v3";
|
||||
|
||||
/// <summary>
|
||||
/// 测试域名。
|
||||
/// </summary>
|
||||
public const string DEVELOPMENT = "https://dev-api-business.tenpay.com/v3";
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
using System;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示腾讯微企付 API 回调通知事件的基类。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class WechatTenpayBusinessEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置 API 版本。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("api_version")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("api_version")]
|
||||
public string? ApiVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置通知 ID。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("event_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("event_id")]
|
||||
public string EventId { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置通知类型。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("event_type")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("event_type")]
|
||||
public string EventType { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置是否是生产环境。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("live_mode")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("live_mode")]
|
||||
public bool IsLiveMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置之前回调通知失败的次数。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("pending_webhooks")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("pending_webhooks")]
|
||||
[System.Text.Json.Serialization.JsonNumberHandling(System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString)]
|
||||
public int PendingWebhookTimes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置通知创建时间。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("create_time")]
|
||||
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.RFC3339DateTimeOffsetConverter))]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("create_time")]
|
||||
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.RFC3339DateTimeOffsetConverter))]
|
||||
public DateTimeOffset CreateTime { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示腾讯微企付 API 回调通知事件的基类。
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class WechatTenpayBusinessEvent<TEventContent> : WechatTenpayBusinessEvent
|
||||
where TEventContent : class, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置通知内容。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("event_content")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("event_content")]
|
||||
public TEventContent EventContent { get; set; } = default!;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
/// <summary>
|
||||
/// 当调用腾讯微企付 API 出错时引发的异常。
|
||||
/// </summary>
|
||||
public class WechatTenpayBusinessException : CommonExceptionBase
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public WechatTenpayBusinessException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WechatTenpayBusinessException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public WechatTenpayBusinessException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示腾讯微企付 API 请求的基类。
|
||||
/// </summary>
|
||||
public abstract class WechatTenpayBusinessRequest : 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>
|
||||
/// 获取或设置请求使用的腾讯微企付敏感字段加密参数。
|
||||
/// <para>如果启用了 <see cref="WechatTenpayBusinessClientOptions.AutoEncryptRequestSensitiveProperty"/> 参数,将由系统自动生成。</para>
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public virtual WechatTenpayBusinessRequestTBEPEncryption? TBEPEncryption { get; set; }
|
||||
}
|
||||
|
||||
public sealed class WechatTenpayBusinessRequestTBEPEncryption
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置加密后的密钥值。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string EncryptedKey { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置 CBC IV。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string IV { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置微企付证书序列号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string CertificateSerialNumber { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置加密算法。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string Algorithm { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
@ -0,0 +1,150 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示腾讯微企付 API 响应的基类。
|
||||
/// </summary>
|
||||
public abstract class WechatTenpayBusinessResponse : 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>
|
||||
/// 获取腾讯微企付 API 返回的敏感字段加密参数。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public WechatTenpayBusinessResponseTBEPEncryption? TBEPEncryption { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取腾讯微企付请求链路 ID。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("trace_id")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("trace_id")]
|
||||
public virtual string? TraceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取腾讯微企付 API 返回的错误详细信息。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("error")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("error")]
|
||||
public virtual WechatTenpayBusinessResponseError? Error { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,该值指示调用腾讯微企付 API 是否成功(即 HTTP 状态码为 200、202 或 204)。
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public virtual bool IsSuccessful()
|
||||
{
|
||||
return (RawStatus == 200 || RawStatus == 202 || RawStatus == 204) && string.IsNullOrEmpty(Error?.Code);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class WechatTenpayBusinessResponseError
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置错误代码。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("code")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("code")]
|
||||
public string? Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置错误描述。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("desc")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("desc")]
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置错误详细信息。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("detail")]
|
||||
[System.Text.Json.Serialization.JsonPropertyName("detail")]
|
||||
public IDictionary<string, string>? Details { get; set; }
|
||||
}
|
||||
|
||||
public sealed class WechatTenpayBusinessResponseTBEPEncryption
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取或设置平台账号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string? PlatformId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置加密后的密钥值。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string? EncryptedKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置 CBC IV。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string? IV { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置平台证书序列号。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string? CertificateSerialNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置加密算法。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string? Algorithm { get; set; }
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Constants
|
||||
{
|
||||
public static class SignAlgorithms
|
||||
public static class SignSchemes
|
||||
{
|
||||
/// <summary>
|
||||
/// WECHATPAY2-SHA256-RSA2048。
|
@ -0,0 +1,72 @@
|
||||
using System;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
{
|
||||
internal static class WechatTenpayClientSignExtensions
|
||||
{
|
||||
public static bool VerifySignature(this WechatTenpayClient client, string strTimestamp, string strNonce, string strBody, string strSignature, string strSerialNumber)
|
||||
{
|
||||
return VerifySignature(client, strTimestamp, strNonce, strBody, strSignature, strSerialNumber, Constants.SignSchemes.WECHATPAY2_SHA256_RSA2048, out _);
|
||||
}
|
||||
|
||||
public static bool VerifySignature(this WechatTenpayClient client, string strTimestamp, string strNonce, string strBody, string strSignature, string strSerialNumber, string strSignScheme)
|
||||
{
|
||||
return VerifySignature(client, strTimestamp, strNonce, strBody, strSignature, strSerialNumber, strSignScheme, out _);
|
||||
}
|
||||
|
||||
public static bool VerifySignature(this WechatTenpayClient client, string strTimestamp, string strNonce, string strBody, string strSignature, string strSerialNumber, out Exception? error)
|
||||
{
|
||||
return VerifySignature(client, strTimestamp, strNonce, strBody, strSignature, strSerialNumber, Constants.SignSchemes.WECHATPAY2_SHA256_RSA2048, out error);
|
||||
}
|
||||
|
||||
public static bool VerifySignature(this WechatTenpayClient client, string strTimestamp, string strNonce, string strBody, string strSignature, string strSerialNumber, string strSignScheme, out Exception? error)
|
||||
{
|
||||
if (client == null) throw new ArgumentNullException(nameof(client));
|
||||
|
||||
switch (strSignScheme)
|
||||
{
|
||||
case Constants.SignSchemes.WECHATPAY2_SHA256_RSA2048:
|
||||
{
|
||||
if (client.PlatformCertificateManager != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cert = client.PlatformCertificateManager.GetEntry(strSerialNumber);
|
||||
if (!cert.HasValue)
|
||||
{
|
||||
error = new Exceptions.WechatTenpayEventVerificationException("There is no platform certificate matched the serial number.");
|
||||
return false;
|
||||
}
|
||||
|
||||
error = null;
|
||||
return Utilities.RSAUtility.VerifyWithSHA256ByCertificate(
|
||||
certificate: cert.Value.Certificate,
|
||||
plainText: GetPlainTextForSignature(timestamp: strTimestamp, nonce: strNonce, body: strBody),
|
||||
signature: strSignature
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
error = new Exception("There is no platform certificate in the certificate manager.");
|
||||
return false;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
error = new Exception("Unsupported sign scheme.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPlainTextForSignature(string timestamp, string nonce, string body)
|
||||
{
|
||||
return $"{timestamp}\n{nonce}\n{body}\n";
|
||||
}
|
||||
}
|
||||
}
|
@ -47,38 +47,10 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
if (callbackSignature == null) throw new ArgumentNullException(nameof(callbackSignature));
|
||||
if (callbackSerialNumber == null) throw new ArgumentNullException(nameof(callbackSerialNumber));
|
||||
|
||||
if (client.PlatformCertificateManager != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cert = client.PlatformCertificateManager.GetEntry(callbackSerialNumber);
|
||||
if (!cert.HasValue)
|
||||
{
|
||||
error = new Exceptions.WechatTenpayEventVerificationException("Verify signature of event failed, because there is no platform certificate matched the serial number.");
|
||||
return false;
|
||||
}
|
||||
|
||||
error = null;
|
||||
return Utilities.RSAUtility.VerifyWithSHA256ByCertificate(
|
||||
certificate: cert.Value.Certificate,
|
||||
plainText: GetPlainTextForSignature(timestamp: callbackTimestamp, nonce: callbackNonce, body: callbackBody),
|
||||
signature: callbackSignature
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = new Exceptions.WechatTenpayEventVerificationException("Verify signature of event failed. Please see the `InnerException` for more details.", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
error = new Exceptions.WechatTenpayEventVerificationException("Verify signature of event failed, because there is no platform certificate in the manager.");
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string GetPlainTextForSignature(string timestamp, string nonce, string body)
|
||||
{
|
||||
return $"{timestamp}\n{nonce}\n{body}\n";
|
||||
bool ret = WechatTenpayClientSignExtensions.VerifySignature(client, callbackTimestamp, callbackNonce, callbackBody, callbackSignature, callbackSerialNumber, out error);
|
||||
if (error != null)
|
||||
error = new Exceptions.WechatTenpayEventVerificationException("Verify signature of event failed. Please see the `InnerException` for more details.", error);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
newValue = Utilities.RSAUtility.EncryptWithECBByCertificate(
|
||||
certificate: certificate,
|
||||
plainText: oldValue,
|
||||
paddingAlgorithm: "PKCS1PADDING"
|
||||
paddingMode: "PKCS1PADDING"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -81,7 +81,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
newValue = Utilities.RSAUtility.DecryptWithECB(
|
||||
privateKey: client.Credentials.MerchantCertificatePrivateKey,
|
||||
cipherText: oldValue,
|
||||
paddingAlgorithm: "PKCS1PADDING"
|
||||
paddingMode: "PKCS1PADDING"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -84,38 +84,10 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
if (responseSignature == null) throw new ArgumentNullException(nameof(responseSignature));
|
||||
if (responseSerialNumber == null) throw new ArgumentNullException(nameof(responseSerialNumber));
|
||||
|
||||
if (client.PlatformCertificateManager != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cert = client.PlatformCertificateManager.GetEntry(responseSerialNumber)!;
|
||||
if (!cert.HasValue)
|
||||
{
|
||||
error = new Exceptions.WechatTenpayResponseVerificationException("Verify signature of response failed, because there is no platform certificate matched the serial number.");
|
||||
return false;
|
||||
}
|
||||
|
||||
error = null;
|
||||
return Utilities.RSAUtility.VerifyWithSHA256ByCertificate(
|
||||
certificate: cert.Value.Certificate,
|
||||
plainText: GetPlainTextForSignature(timestamp: responseTimestamp, nonce: responseNonce, body: responseBody),
|
||||
signature: responseSignature
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = new Exceptions.WechatTenpayResponseVerificationException("Verify signature of response failed. Please see the `InnerException` for more details.", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
error = new Exceptions.WechatTenpayResponseVerificationException("Verify signature of response failed, because there is no platform certificate in the manager.");
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string GetPlainTextForSignature(string timestamp, string nonce, string body)
|
||||
{
|
||||
return $"{timestamp}\n{nonce}\n{body}\n";
|
||||
bool ret = WechatTenpayClientSignExtensions.VerifySignature(client, responseTimestamp, responseNonce, responseBody, responseSignature, responseSerialNumber, out error);
|
||||
if (error != null)
|
||||
error = new Exceptions.WechatTenpayEventVerificationException("Verify signature of response failed. Please see the `InnerException` for more details.", error);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Interceptors
|
||||
|
||||
switch (_scheme)
|
||||
{
|
||||
case Constants.SignAlgorithms.WECHATPAY2_SHA256_RSA2048:
|
||||
case Constants.SignSchemes.WECHATPAY2_SHA256_RSA2048:
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -113,15 +113,15 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
|
||||
/// </summary>
|
||||
/// <param name="privateKeyBytes">PKCS#8 私钥字节数据。</param>
|
||||
/// <param name="cipherBytes">待解密的数据字节数据。</param>
|
||||
/// <param name="paddingAlgorithm">填充算法。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/>)</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/>)</param>
|
||||
/// <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 paddingMode = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1)
|
||||
{
|
||||
if (privateKeyBytes == null) throw new ArgumentNullException(nameof(privateKeyBytes));
|
||||
if (cipherBytes == null) throw new ArgumentNullException(nameof(cipherBytes));
|
||||
|
||||
RsaKeyParameters rsaKeyParams = (RsaKeyParameters)PrivateKeyFactory.CreateKey(privateKeyBytes);
|
||||
return DecryptWithECB(rsaKeyParams, cipherBytes, paddingAlgorithm);
|
||||
return DecryptWithECB(rsaKeyParams, cipherBytes, paddingMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -129,16 +129,16 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
|
||||
/// </summary>
|
||||
/// <param name="privateKey">PKCS#8 私钥(PEM 格式)。</param>
|
||||
/// <param name="cipherText">经 Base64 编码的待解密数据。</param>
|
||||
/// <param name="paddingAlgorithm">填充算法。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/>)</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/>)</param>
|
||||
/// <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 paddingMode = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1)
|
||||
{
|
||||
if (privateKey == null) throw new ArgumentNullException(nameof(privateKey));
|
||||
if (cipherText == null) throw new ArgumentNullException(nameof(cipherText));
|
||||
|
||||
byte[] privateKeyBytes = ConvertPkcs8PrivateKeyToByteArray(privateKey);
|
||||
byte[] cipherBytes = Convert.FromBase64String(cipherText);
|
||||
byte[] plainBytes = DecryptWithECB(privateKeyBytes, cipherBytes, paddingAlgorithm);
|
||||
byte[] plainBytes = DecryptWithECB(privateKeyBytes, cipherBytes, paddingMode);
|
||||
return Encoding.UTF8.GetString(plainBytes);
|
||||
}
|
||||
|
||||
@ -147,15 +147,15 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
|
||||
/// </summary>
|
||||
/// <param name="publicKeyBytes">PKCS#8 公钥字节数据。</param>
|
||||
/// <param name="plainBytes">待加密的数据字节数据。</param>
|
||||
/// <param name="paddingAlgorithm">填充算法。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/>)</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/>)</param>
|
||||
/// <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 paddingMode = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1)
|
||||
{
|
||||
if (publicKeyBytes == null) throw new ArgumentNullException(nameof(publicKeyBytes));
|
||||
if (plainBytes == null) throw new ArgumentNullException(nameof(plainBytes));
|
||||
|
||||
RsaKeyParameters rsaKeyParams = (RsaKeyParameters)PublicKeyFactory.CreateKey(publicKeyBytes);
|
||||
return EncryptWithECB(rsaKeyParams, plainBytes, paddingAlgorithm);
|
||||
return EncryptWithECB(rsaKeyParams, plainBytes, paddingMode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -163,16 +163,16 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
|
||||
/// </summary>
|
||||
/// <param name="publicKey">PKCS#8 公钥(PEM 格式)。</param>
|
||||
/// <param name="plainText">待加密的文本数据。</param>
|
||||
/// <param name="paddingAlgorithm">填充算法。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/>)</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/>)</param>
|
||||
/// <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 paddingMode = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1)
|
||||
{
|
||||
if (publicKey == null) throw new ArgumentNullException(nameof(publicKey));
|
||||
if (plainText == null) throw new ArgumentNullException(nameof(plainText));
|
||||
|
||||
byte[] publicKeyBytes = ConvertPkcs8PublicKeyToByteArray(publicKey);
|
||||
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
|
||||
byte[] cipherBytes = EncryptWithECB(publicKeyBytes, plainBytes, paddingAlgorithm);
|
||||
byte[] cipherBytes = EncryptWithECB(publicKeyBytes, plainBytes, paddingMode);
|
||||
return Convert.ToBase64String(cipherBytes);
|
||||
}
|
||||
|
||||
@ -181,16 +181,16 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
|
||||
/// </summary>
|
||||
/// <param name="certificate">证书(PEM 格式)。</param>
|
||||
/// <param name="plainText">待加密的文本数据。</param>
|
||||
/// <param name="paddingAlgorithm">填充算法。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/>)</param>
|
||||
/// <param name="paddingMode">填充模式。(默认值:<see cref="RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1"/>)</param>
|
||||
/// <returns>经 Base64 编码的加密数据。</returns>
|
||||
public static string EncryptWithECBByCertificate(string certificate, string plainText, string paddingAlgorithm = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1)
|
||||
public static string EncryptWithECBByCertificate(string certificate, string plainText, string paddingMode = RSA_CIPHER_PADDING_OAEP_WITH_SHA1_AND_MGF1)
|
||||
{
|
||||
if (certificate == null) throw new ArgumentNullException(nameof(certificate));
|
||||
if (plainText == null) throw new ArgumentNullException(nameof(plainText));
|
||||
|
||||
RsaKeyParameters rsaKeyParams = ConvertCertificateToPublicKeyParams(certificate);
|
||||
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
|
||||
byte[] cipherBytes = EncryptWithECB(rsaKeyParams, plainBytes, paddingAlgorithm);
|
||||
byte[] cipherBytes = EncryptWithECB(rsaKeyParams, plainBytes, paddingMode);
|
||||
return Convert.ToBase64String(cipherBytes);
|
||||
}
|
||||
|
||||
@ -305,16 +305,16 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Utilities
|
||||
return signer.VerifySignature(signBytes);
|
||||
}
|
||||
|
||||
private static byte[] EncryptWithECB(RsaKeyParameters rsaKeyParams, byte[] plainBytes, string paddingAlgorithm)
|
||||
private static byte[] EncryptWithECB(RsaKeyParameters rsaKeyParams, byte[] plainBytes, string paddingMode)
|
||||
{
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher($"{RSA_CIPHER_ALGORITHM_ECB}/{paddingAlgorithm}");
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher($"{RSA_CIPHER_ALGORITHM_ECB}/{paddingMode}");
|
||||
cipher.Init(true, rsaKeyParams);
|
||||
return cipher.DoFinal(plainBytes);
|
||||
}
|
||||
|
||||
private static byte[] DecryptWithECB(RsaKeyParameters rsaKeyParams, byte[] cipherBytes, string paddingAlgorithm)
|
||||
private static byte[] DecryptWithECB(RsaKeyParameters rsaKeyParams, byte[] cipherBytes, string paddingMode)
|
||||
{
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher($"{RSA_CIPHER_ALGORITHM_ECB}/{paddingAlgorithm}");
|
||||
IBufferedCipher cipher = CipherUtilities.GetCipher($"{RSA_CIPHER_ALGORITHM_ECB}/{paddingMode}");
|
||||
cipher.Init(false, rsaKeyParams);
|
||||
return cipher.DoFinal(cipherBytes);
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
FlurlClient.WithTimeout(TimeSpan.FromMilliseconds(options.Timeout));
|
||||
|
||||
Interceptors.Add(new Interceptors.WechatTenpayRequestSignatureInterceptor(
|
||||
scheme: options.SignAlgorithm,
|
||||
scheme: options.SignScheme,
|
||||
mchId: options.MerchantId,
|
||||
mchCertSn: options.MerchantCertificateSerialNumber,
|
||||
mchCertPk: options.MerchantCertificatePrivateKey
|
||||
|
@ -34,9 +34,9 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置微信支付 API 签名认证方式。
|
||||
/// <para>默认值:<see cref="Constants.SignAlgorithms.WECHATPAY2_SHA256_RSA2048"/></para>
|
||||
/// <para>默认值:<see cref="Constants.SignSchemes.WECHATPAY2_SHA256_RSA2048"/></para>
|
||||
/// </summary>
|
||||
public string SignAlgorithm { get; set; } = Constants.SignAlgorithms.WECHATPAY2_SHA256_RSA2048;
|
||||
public string SignScheme { get; set; } = Constants.SignSchemes.WECHATPAY2_SHA256_RSA2048;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置微信支付商户号。
|
||||
@ -51,7 +51,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
/// <summary>
|
||||
/// <i>(本参数已废弃,请使用 <see cref="MerchantCertificateSerialNumber"/> 参数)</i>
|
||||
/// </summary>
|
||||
[Obsolete("本参数已废弃,请使用 `MerchantCertificateSerialNumber` 参数")]
|
||||
[Obsolete("本参数已废弃,请使用 `MerchantCertificateSerialNumber` 参数", error: true)]
|
||||
public string MerchantCertSerialNumber { get { return MerchantCertificateSerialNumber; } set { MerchantCertificateSerialNumber = value; } }
|
||||
|
||||
/// <summary>
|
||||
@ -62,7 +62,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
/// <summary>
|
||||
/// <i>(本参数已废弃,请使用 <see cref="MerchantCertificatePrivateKey"/> 参数)</i>
|
||||
/// </summary>
|
||||
[Obsolete("本参数已废弃,请使用 `MerchantCertificatePrivateKey` 参数")]
|
||||
[Obsolete("本参数已废弃,请使用 `MerchantCertificatePrivateKey` 参数", error: true)]
|
||||
public string MerchantCertPrivateKey { get { return MerchantCertificatePrivateKey; } set { MerchantCertificatePrivateKey = value; } }
|
||||
|
||||
/// <summary>
|
||||
@ -85,7 +85,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
/// <summary>
|
||||
/// <i>(本参数已废弃,请使用 <see cref="PlatformCertificateManager"/> 参数)</i>
|
||||
/// </summary>
|
||||
[Obsolete("本参数已废弃,请使用 `PlatformCertificateManager` 参数")]
|
||||
[Obsolete("本参数已废弃,请使用 `PlatformCertificateManager` 参数", error: true)]
|
||||
public Settings.CertificateManager CertificateManager { get { return PlatformCertificateManager; } set { PlatformCertificateManager = value; } }
|
||||
|
||||
/// <summary>
|
||||
|
@ -17,7 +17,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
/// <summary>
|
||||
/// <i>(本参数已废弃,请使用 <see cref="WechatpayCertificateSerialNumber"/> 参数)</i>
|
||||
/// </summary>
|
||||
[Obsolete("本参数已废弃,请使用 `WechatpayCertificateSerialNumber` 参数")]
|
||||
[Obsolete("本参数已废弃,请使用 `WechatpayCertificateSerialNumber` 参数", error: true)]
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string? WechatpayCertSerialNumber { get { return WechatpayCertificateSerialNumber; } set { WechatpayCertificateSerialNumber = value; } }
|
||||
|
@ -87,7 +87,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3
|
||||
/// <summary>
|
||||
/// <i>(本参数已废弃,请使用 <see cref="WechatpayCertificateSerialNumber"/> 参数)</i>
|
||||
/// </summary>
|
||||
[Obsolete("本参数已废弃,请使用 `WechatpayCertificateSerialNumber` 参数")]
|
||||
[Obsolete("本参数已废弃,请使用 `WechatpayCertificateSerialNumber` 参数", error: true)]
|
||||
[Newtonsoft.Json.JsonIgnore]
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public string WechatpayCertSerialNumber { get { return WechatpayCertificateSerialNumber; } set { WechatpayCertificateSerialNumber = value; } }
|
||||
|
1
test/SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests/.gitignore
vendored
Normal file
1
test/SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
appsettings.local.json
|
@ -0,0 +1,18 @@
|
||||
{
|
||||
"api_version": "",
|
||||
"event_id": "",
|
||||
"event_type": "pay.succeeded",
|
||||
"live_mode": false,
|
||||
"pending_webhooks": 0,
|
||||
"create_time": "2020-01-01T01:02:03+08:00",
|
||||
"event_content": {
|
||||
"payment_id": "",
|
||||
"purchaser_id": "",
|
||||
"out_payment_id": "",
|
||||
"bargainor_ent_id": "",
|
||||
"amount": 0,
|
||||
"status": "",
|
||||
"pay_succ_time": "2020-01-01T01:02:03+08:00",
|
||||
"failed_reason": ""
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
{
|
||||
"api_version": "",
|
||||
"event_id": "",
|
||||
"event_type": "product.application.finish",
|
||||
"live_mode": false,
|
||||
"pending_webhooks": 0,
|
||||
"create_time": "2020-01-01T01:02:03+08:00",
|
||||
"event_content": {
|
||||
"request_no": "",
|
||||
"out_request_no": "",
|
||||
"ent_id": "",
|
||||
"status": "",
|
||||
"product_details": [
|
||||
{
|
||||
"product_name": "",
|
||||
"status": "",
|
||||
"accounts": [
|
||||
{
|
||||
"ent_acct_id": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"file_id": ""
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"query_date": "2021-01-01"
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"download_url": "",
|
||||
"expire_time": "2021-06-08T10:34:56+08:00",
|
||||
"bill_status": ""
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"close_reason": ""
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"payment_id": "",
|
||||
"out_payment_id": "",
|
||||
"status": "",
|
||||
"close_time": "2021-06-08T10:34:56+08:00"
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
{
|
||||
"purchaser_type": "",
|
||||
"out_payment_id": "",
|
||||
"amount": 0,
|
||||
"currency": "",
|
||||
"expire_time": "2021-06-08T10:34:56+08:00",
|
||||
"payee": {
|
||||
"ent_id": "",
|
||||
"ent_name": "",
|
||||
"ent_acct_id": "",
|
||||
"bank_account_number_last4": ""
|
||||
},
|
||||
"memo": "",
|
||||
"goods": [
|
||||
{
|
||||
"good_name": "",
|
||||
"good_number": "0",
|
||||
"good_amount": "0"
|
||||
}
|
||||
],
|
||||
"attachment": "",
|
||||
"risk_control": {
|
||||
"device_id": "",
|
||||
"payer_client_ip": "",
|
||||
"payer_ua": "",
|
||||
"create_time": "2021-06-08T10:34:56+08:00",
|
||||
"pick_type": "",
|
||||
"pick_description": ""
|
||||
},
|
||||
"notify_url": {
|
||||
"server_notify_url": ""
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
{
|
||||
"payment_id": "",
|
||||
"out_payment_id": "",
|
||||
"amount": 0,
|
||||
"currency": "",
|
||||
"payee": {
|
||||
"ent_id": "",
|
||||
"ent_name": "",
|
||||
"ent_acct_id": "",
|
||||
"bank_account_number_last4": ""
|
||||
},
|
||||
"pay_status": "",
|
||||
"memo": ""
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
{
|
||||
"payment_id": "",
|
||||
"out_payment_id": "",
|
||||
"user_openid": "",
|
||||
"amount": 0,
|
||||
"currency": "",
|
||||
"payee": {
|
||||
"ent_id": "",
|
||||
"ent_name": "",
|
||||
"ent_acct_id": "",
|
||||
"bank_account_number_last4": ""
|
||||
},
|
||||
"pay_status": "",
|
||||
"memo": "",
|
||||
"failed_reason": {
|
||||
"failed_type": "",
|
||||
"failed_detail": ""
|
||||
},
|
||||
"attachment": ""
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"pc_web": {
|
||||
"url": "",
|
||||
"expire_time": "2020-01-01T01:02:03+08:00"
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
{
|
||||
"out_request_no": "",
|
||||
"business_license": {
|
||||
"business_register_type": "",
|
||||
"unified_social_credit_code": "",
|
||||
"merchant_name": "",
|
||||
"merchant_short_name": "",
|
||||
"legal_person_name": "",
|
||||
"validity_period": [ "2024-01-01", "长期" ],
|
||||
"photocopy_file_id": ""
|
||||
},
|
||||
"legal_person_id_card": {
|
||||
"name": "",
|
||||
"number": "",
|
||||
"validity_period": [ "2024-01-01", "长期" ],
|
||||
"front_photocopy_file_id": "",
|
||||
"back_photocopy_file_id": ""
|
||||
},
|
||||
"contact_info": {
|
||||
"mobile_number": ""
|
||||
},
|
||||
"payee_accounts": [
|
||||
{
|
||||
"account_type": "",
|
||||
"bank_account_name": "",
|
||||
"bank_account_number": "",
|
||||
"bank_name": "",
|
||||
"bank_branch_id": "",
|
||||
"bank_branch_name": ""
|
||||
}
|
||||
],
|
||||
"products": [
|
||||
{
|
||||
"product_name": ""
|
||||
}
|
||||
],
|
||||
"notify_url": {
|
||||
"server_notify_url": "",
|
||||
"web_success_url": "",
|
||||
"web_refresh_url": ""
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"request_no": "",
|
||||
"out_request_no": ""
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"request_no": "",
|
||||
"out_request_no": "",
|
||||
"ent_id": "",
|
||||
"status": "",
|
||||
"product_details": [
|
||||
{
|
||||
"product_name": "",
|
||||
"status": "",
|
||||
"accounts": [
|
||||
{
|
||||
"ent_acct_id": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
{
|
||||
"id": ""
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
{
|
||||
"pc_web": {
|
||||
"url": "",
|
||||
"expire_time": "2020-01-01T01:02:03+08:00"
|
||||
},
|
||||
"pc_plugin": {
|
||||
"key": "",
|
||||
"expire_time": "2020-01-01T01:02:03+08:00"
|
||||
},
|
||||
"wx_qrcode": {
|
||||
"url": ""
|
||||
},
|
||||
"miniprogram": {
|
||||
"mp_path": "",
|
||||
"mp_appid": "",
|
||||
"mp_username": "",
|
||||
"expire_time": "2020-01-01T01:02:03+08:00"
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net472; net6.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<NullableReferenceTypes>true</NullableReferenceTypes>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove=".gitignore" />
|
||||
<None Remove="appsettings.local.json" />
|
||||
<Content Include="appsettings.json">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="appsettings.*.json" Condition="'$(Configuration)' == 'Debug'">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
</Content>
|
||||
<Content Include="ModelSamples/**/*.json" />
|
||||
<Content Include="EventSamples/**/*.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="SKIT.FlurlHttpClient.Tools.CodeAnalyzer" Version="0.1.0-alpha.1" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\SKIT.FlurlHttpClient.Wechat.TenpayBusiness\SKIT.FlurlHttpClient.Wechat.TenpayBusiness.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
@ -0,0 +1,26 @@
|
||||
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();
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests
|
||||
{
|
||||
public class TestCase_SM3UtilityTests
|
||||
{
|
||||
[Fact(DisplayName = "测试用例:计算 SM3 哈希值")]
|
||||
public void TestSM3Hash()
|
||||
{
|
||||
byte[] msgBytes = Convert.FromBase64String("QXdlc29tZSBTS0lULkZsdXJsSHR0cENsaWVudC5XZWNoYXQuVGVucGF5QnVzaW5lc3Mh");
|
||||
string expectedHashText = "A7A58FCEDDDEE4BD2E05887E5F4D8B7D662357BE474F3821CA858EE1CFFB4B83";
|
||||
string actualHashText = BitConverter.ToString(Utilities.SM3Utility.Hash(msgBytes)).Replace("-", "");
|
||||
|
||||
Assert.Equal(actualHashText, expectedHashText, ignoreCase: true);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
using Xunit;
|
||||
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests
|
||||
{
|
||||
public class TestCase_SM4UtilityTests
|
||||
{
|
||||
[Fact(DisplayName = "测试用例:SM4 加密")]
|
||||
public void TestSM4Encrypt()
|
||||
{
|
||||
string key = "MDAwMDAwMDAwMDAwMDAwMA==";
|
||||
string iv = "OGE2YzRkZGQ4YTZjNGRkZA==";
|
||||
string plainText = "Awesome SKIT.FlurlHttpClient.Wechat.TenpayBusiness!";
|
||||
|
||||
string actualCipher = Utilities.SM4Utility.EncryptWithCBC(key: key, iv: iv, plainText: plainText);
|
||||
string expectedCipher = "Fm3z4Ipjuaj4oQLfxpTrvoZm5JdbjvjrJo3PRhvSsOppk8/PN+izH3Wo9Rz6V85mpq6X1cGul8U7jjaAl1PWpg==";
|
||||
|
||||
Assert.Equal(expectedCipher, actualCipher);
|
||||
}
|
||||
|
||||
[Fact(DisplayName = "测试用例:SM4 解密")]
|
||||
public void TestSM4Decrypt()
|
||||
{
|
||||
string key = "MDAwMDAwMDAwMDAwMDAwMA==";
|
||||
string iv = "OGE2YzRkZGQ4YTZjNGRkZA==";
|
||||
string cipherText = "Fm3z4Ipjuaj4oQLfxpTrvoZm5JdbjvjrJo3PRhvSsOppk8/PN+izH3Wo9Rz6V85mpq6X1cGul8U7jjaAl1PWpg==";
|
||||
|
||||
string actualPlain = Utilities.SM4Utility.DecryptWithCBC(key: key, iv: iv, cipherText: cipherText);
|
||||
string expectedPlain = "Awesome SKIT.FlurlHttpClient.Wechat.TenpayBusiness!";
|
||||
|
||||
Assert.Equal(expectedPlain, actualPlain);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
namespace SKIT.FlurlHttpClient.Wechat.TenpayBusiness.UnitTests
|
||||
{
|
||||
internal class TestClients
|
||||
{
|
||||
static TestClients()
|
||||
{
|
||||
Instance = new WechatTenpayBusinessClient(new WechatTenpayBusinessClientOptions()
|
||||
{
|
||||
PlatformId = TestConfigs.WechatPlatformId,
|
||||
PlatformCertificateSerialNumber = TestConfigs.WechatPlatformCertSerialNumber,
|
||||
PlatformCertificatePrivateKey = TestConfigs.WechatPlatformCertPrivateKey,
|
||||
TBEPCertificateSerialNumber = TestConfigs.WechatTBEPCertSerialNumber,
|
||||
TBEPCertificatePublicKey = TestConfigs.WechatTBEPCertPrivateKey
|
||||
});
|
||||
}
|
||||
|
||||
public static readonly WechatTenpayBusinessClient Instance;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user