feat(tenpayv2): 导入项目

This commit is contained in:
RHQYZ 2022-01-20 23:20:03 +08:00 committed by GitHub
parent 47575857b6
commit 7205686f71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
72 changed files with 4092 additions and 45 deletions

BIN
LOGO.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -12,6 +12,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{3E34ADB9-1F5
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.Api", "src\SKIT.FlurlHttpClient.Wechat.Api\SKIT.FlurlHttpClient.Wechat.Api.csproj", "{082C1F69-7932-473F-A700-49584371BE8C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.TenpayV2", "src\SKIT.FlurlHttpClient.Wechat.TenpayV2\SKIT.FlurlHttpClient.Wechat.TenpayV2.csproj", "{18DEF654-1EDF-46C7-8430-685D6236E9C5}"
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.Work", "src\SKIT.FlurlHttpClient.Wechat.Work\SKIT.FlurlHttpClient.Wechat.Work.csproj", "{CDD123E6-2622-4368-BAEE-8B95F05F1AB2}"
@ -28,6 +30,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SKIT.FlurlHttpClient.Wechat.Api.UnitTests", "test\SKIT.FlurlHttpClient.Wechat.Api.UnitTests\SKIT.FlurlHttpClient.Wechat.Api.UnitTests.csproj", "{0C87A7D9-26EA-4821-AF3F-6D28B3006B24}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SKIT.FlurlHttpClient.Wechat.TenpayV2.UnitTests", "test\SKIT.FlurlHttpClient.Wechat.TenpayV2.UnitTests\SKIT.FlurlHttpClient.Wechat.TenpayV2.UnitTests.csproj", "{574A567A-6D2C-49F6-9A98-0133CA9B007D}"
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.Work.UnitTests", "test\SKIT.FlurlHttpClient.Wechat.Work.UnitTests\SKIT.FlurlHttpClient.Wechat.Work.UnitTests.csproj", "{DBF84F66-1436-4599-93AB-7C16A3A2C3A4}"
@ -54,6 +58,10 @@ Global
{082C1F69-7932-473F-A700-49584371BE8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{082C1F69-7932-473F-A700-49584371BE8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{082C1F69-7932-473F-A700-49584371BE8C}.Release|Any CPU.Build.0 = Release|Any CPU
{18DEF654-1EDF-46C7-8430-685D6236E9C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{18DEF654-1EDF-46C7-8430-685D6236E9C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{18DEF654-1EDF-46C7-8430-685D6236E9C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{18DEF654-1EDF-46C7-8430-685D6236E9C5}.Release|Any CPU.Build.0 = Release|Any CPU
{6FE502D4-C43D-49C9-9E57-D1EE566FD1C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
@ -106,12 +114,17 @@ Global
{7667F0D3-B41D-43C2-B69D-A68FE230EBF7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7667F0D3-B41D-43C2-B69D-A68FE230EBF7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7667F0D3-B41D-43C2-B69D-A68FE230EBF7}.Release|Any CPU.Build.0 = Release|Any CPU
{574A567A-6D2C-49F6-9A98-0133CA9B007D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{574A567A-6D2C-49F6-9A98-0133CA9B007D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{574A567A-6D2C-49F6-9A98-0133CA9B007D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{574A567A-6D2C-49F6-9A98-0133CA9B007D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{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}
{CDD123E6-2622-4368-BAEE-8B95F05F1AB2} = {3E34ADB9-1F52-4C96-9A42-DE782DE1AAA3}
{7F155EFB-152F-4798-9984-99102B21D2F8} = {3E34ADB9-1F52-4C96-9A42-DE782DE1AAA3}
@ -126,6 +139,7 @@ Global
{D1B321C9-3004-4645-A78D-A85C152062FA} = {35C901ED-C234-4A91-9561-AD89B3BB788D}
{65E51735-73CE-4E9B-AA65-4BF5E4C8A705} = {35C901ED-C234-4A91-9561-AD89B3BB788D}
{7667F0D3-B41D-43C2-B69D-A68FE230EBF7} = {35C901ED-C234-4A91-9561-AD89B3BB788D}
{574A567A-6D2C-49F6-9A98-0133CA9B007D} = {C95AF531-CF44-44AA-AC90-F4DF9F941674}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F08ED64E-2517-4B51-A4BE-D33D56CC7B39}

View File

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

View File

@ -1,5 +1,10 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Nodes;
namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models.Platform
{
@ -38,7 +43,8 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models.Platform
/// </summary>
[Newtonsoft.Json.JsonProperty("norm")]
[System.Text.Json.Serialization.JsonPropertyName("norm")]
public object Norm { get; set; } = default!;
[System.Text.Json.Serialization.JsonConverter(typeof(DynamicObjectConverter))]
public dynamic Norm { get; set; } = default!;
}
}
@ -49,4 +55,110 @@ namespace SKIT.FlurlHttpClient.Wechat.OpenAI.Models.Platform
[System.Text.Json.Serialization.JsonPropertyName("result")]
public Types.Result[] ResultList { get; set; } = default!;
}
public class DynamicObjectConverter : JsonConverter<dynamic?>
{
public override bool CanConvert(Type typeToConvert)
{
return base.CanConvert(typeToConvert) || typeof(IDynamicMetaObjectProvider).IsAssignableFrom(typeToConvert);
}
public override dynamic? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return ReadValue(ref reader, options);
}
public override void Write(Utf8JsonWriter writer, dynamic? value, JsonSerializerOptions options)
{
}
private object? ReadValue(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
switch (reader.TokenType)
{
case JsonTokenType.None:
case JsonTokenType.Null:
return null;
case JsonTokenType.True:
return true;
case JsonTokenType.False:
return false;
case JsonTokenType.Number:
return reader.TryGetInt64(out long longValue) ? longValue : reader.GetDouble();
case JsonTokenType.String:
return reader.GetString();
case JsonTokenType.StartObject:
return ReadObject(ref reader, options);
case JsonTokenType.StartArray:
return ReadArray(ref reader, options);
default:
return JsonNode.Parse(ref reader, new JsonNodeOptions() { PropertyNameCaseInsensitive = options.PropertyNameCaseInsensitive });
}
}
private object? ReadObject(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
IDictionary<string, object?> expandoObject = new ExpandoObject();
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonTokenType.PropertyName:
{
string key = reader.GetString()!;
if (!reader.Read())
{
throw new JsonException("Unexpected end when reading ExpandoObject.");
}
object? value = ReadValue(ref reader, options);
expandoObject[key] = value;
}
break;
case JsonTokenType.Comment:
break;
case JsonTokenType.EndObject:
return expandoObject;
}
}
throw new JsonException("Unexpected end when reading ExpandoObject.");
}
private object? ReadArray(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
IList<object?> list = new List<object?>();
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonTokenType.Comment:
break;
case JsonTokenType.EndArray:
return list.ToArray();
default:
{
object? element = ReadValue(ref reader, options);
list.Add(element);
}
break;
}
}
throw new JsonException("Unexpected end when reading ExpandoObject.");
}
}
}

View File

@ -0,0 +1,15 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Constants
{
public static class SignTypes
{
/// <summary>
/// MD5。
/// </summary>
public const string MD5 = "MD5";
/// <summary>
/// HMAC-SHA256。
/// </summary>
public const string HMAC_SHA256 = "HMAC-SHA256";
}
}

View File

@ -0,0 +1,29 @@
using System;
namespace Newtonsoft.Json.Converters
{
internal class YesOrNoBooleanConverter : JsonConverter<bool>
{
private readonly JsonConverter<bool?> _converter = new YesOrNoNullableBooleanConverter();
public override bool CanRead
{
get { return true; }
}
public override bool CanWrite
{
get { return true; }
}
public override bool ReadJson(JsonReader reader, Type objectType, bool existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return _converter.ReadJson(reader, objectType, existingValue, hasExistingValue, serializer) ?? default;
}
public override void WriteJson(JsonWriter writer, bool value, JsonSerializer serializer)
{
_converter.WriteJson(writer, value, serializer);
}
}
}

View File

@ -0,0 +1,50 @@
using System;
namespace Newtonsoft.Json.Converters
{
internal class YesOrNoNullableBooleanConverter : JsonConverter<bool?>
{
public override bool CanRead
{
get { return true; }
}
public override bool CanWrite
{
get { return true; }
}
public override bool? ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, bool? existingValue, bool hasExistingValue, Newtonsoft.Json.JsonSerializer serializer)
{
if (reader.TokenType == Newtonsoft.Json.JsonToken.Null)
{
return existingValue;
}
else if (reader.TokenType == Newtonsoft.Json.JsonToken.Boolean)
{
return serializer.Deserialize<bool>(reader);
}
else if (reader.TokenType == Newtonsoft.Json.JsonToken.String)
{
string? value = serializer.Deserialize<string>(reader);
if (value == null)
return existingValue;
if ("Y".Equals(value))
return true;
else if ("N".Equals(value))
return false;
}
throw new Newtonsoft.Json.JsonReaderException();
}
public override void WriteJson(Newtonsoft.Json.JsonWriter writer, bool? value, Newtonsoft.Json.JsonSerializer serializer)
{
if (value.HasValue)
writer.WriteValue(value.Value ? "Y" : "N");
else
writer.WriteNull();
}
}
}

View File

@ -0,0 +1,29 @@
using System;
namespace Newtonsoft.Json.Converters
{
internal class PureDigitalTextDateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{
private readonly JsonConverter<DateTimeOffset?> _converter = new PureDigitalTextNullableDateTimeOffsetConverter();
public override bool CanRead
{
get { return true; }
}
public override bool CanWrite
{
get { return true; }
}
public override DateTimeOffset ReadJson(JsonReader reader, Type objectType, DateTimeOffset existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return _converter.ReadJson(reader, objectType, existingValue, hasExistingValue, serializer) ?? default;
}
public override void WriteJson(JsonWriter writer, DateTimeOffset value, JsonSerializer serializer)
{
_converter.WriteJson(writer, value, serializer);
}
}
}

View File

@ -0,0 +1,55 @@
using System;
using System.Globalization;
namespace Newtonsoft.Json.Converters
{
internal class PureDigitalTextNullableDateTimeOffsetConverter : JsonConverter<DateTimeOffset?>
{
internal const string DATETIME_FORMAT = "yyyyMMddHHmmss";
public override bool CanRead
{
get { return true; }
}
public override bool CanWrite
{
get { return true; }
}
public override DateTimeOffset? ReadJson(JsonReader reader, Type objectType, DateTimeOffset? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return existingValue;
}
else if (reader.TokenType == JsonToken.String)
{
string? value = serializer.Deserialize<string>(reader);
if (value == null)
return existingValue;
if (DateTimeOffset.TryParseExact(value, DATETIME_FORMAT, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out DateTimeOffset result))
return result;
if (DateTimeOffset.TryParse(value, out result))
return result;
}
else if (reader.TokenType == JsonToken.Date)
{
reader.DateFormatString = DATETIME_FORMAT;
return serializer.Deserialize<DateTimeOffset>(reader);
}
throw new JsonReaderException();
}
public override void WriteJson(JsonWriter writer, DateTimeOffset? value, JsonSerializer serializer)
{
if (value.HasValue)
writer.WriteValue(value.Value.ToString(DATETIME_FORMAT, DateTimeFormatInfo.InvariantInfo));
else
writer.WriteNull();
}
}
}

View File

@ -0,0 +1,222 @@
using System;
using System.Collections;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Newtonsoft.Json.Linq;
namespace Newtonsoft.Json.Converters
{
internal static class FlattenNArrayObjectConverterBase
{
public const string PROPERTY_WILDCARD_NARRAY_ELEMENT = "$n";
public const string PROPERTY_NAME_NARRAY = "#n";
}
internal abstract partial class FlattenNArrayObjectConverterBase<T> : JsonConverter<T?>
where T : class, new()
{
private sealed class InnerTypedJsonProperty
{
public string PropertyName { get; }
public PropertyInfo PropertyInfo { get; }
public Type PropertyType { get { return PropertyInfo.PropertyType; } }
public bool IsNArrayProperty { get; }
public InnerTypedJsonProperty(string propertyName, PropertyInfo propertyInfo, bool isNArrayProperty)
{
PropertyName = propertyName;
PropertyInfo = propertyInfo;
IsNArrayProperty = isNArrayProperty;
}
}
private const string PROPERTY_WILDCARD_NARRAY_ELEMENT = FlattenNArrayObjectConverterBase.PROPERTY_WILDCARD_NARRAY_ELEMENT;
private const string PROPERTY_NAME_NARRAY = FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY;
private static readonly Hashtable _mappedTypeJsonProperties = new Hashtable();
public override bool CanRead
{
get { return true; }
}
public override bool CanWrite
{
get { return true; }
}
public override T? ReadJson(JsonReader reader, Type objectType, T? existingValue, bool hasExistingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return existingValue;
}
else if (reader.TokenType == JsonToken.StartObject)
{
InnerTypedJsonProperty[] typedJsonProperties = GetTypedJsonProperties(objectType);
if (typedJsonProperties.Count(p => p.IsNArrayProperty) != 1)
throw new JsonSerializationException("The number of `$n` properties must be only one.");
JObject jObject = JObject.Load(reader);
T tObject = new T();
foreach (JProperty jKey in jObject.Properties())
{
InnerTypedJsonProperty? typedJsonProperty = typedJsonProperties.SingleOrDefault(e => e.PropertyName == jKey.Name);
if (typedJsonProperty != null)
{
// 处理普通属性
object? value = serializer is null ?
jObject[typedJsonProperty.PropertyName]?.ToObject(typedJsonProperty.PropertyType) :
jObject[typedJsonProperty.PropertyName]?.ToObject(typedJsonProperty.PropertyType, serializer);
typedJsonProperty.PropertyInfo.SetValue(tObject, value);
}
else if (TryMatchNArrayIndex(jKey.Name, out int index))
{
// 处理 $n 属性
InnerTypedJsonProperty narrayJsonProperty = typedJsonProperties.Single(e => e.IsNArrayProperty);
object? value = narrayJsonProperty.PropertyInfo.GetValue(tObject);
Array array = CreateOrExpandNArray(value, narrayJsonProperty.PropertyType.GetElementType()!, index + 1);
object? element = CreateOrUpdateNArrayElement(array, index, jKey.Name, jKey.Value, serializer);
narrayJsonProperty.PropertyInfo.SetValue(tObject, array);
}
else if (serializer?.MissingMemberHandling == MissingMemberHandling.Error)
{
throw new JsonSerializationException($"Could not find member `{jKey.Name}` on object of type `{objectType.Name}`.");
}
}
return tObject;
}
throw new JsonSerializationException();
}
public override void WriteJson(JsonWriter writer, T? value, JsonSerializer serializer)
{
if (value is null)
{
writer.WriteNull();
return;
}
throw new NotImplementedException();
}
private static InnerTypedJsonProperty[] GetTypedJsonProperties(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
string mappedTypeKey = type.AssemblyQualifiedName ?? type.GetHashCode().ToString();
InnerTypedJsonProperty[]? typedJsonProperties = (InnerTypedJsonProperty[]?)_mappedTypeJsonProperties[mappedTypeKey];
if (typedJsonProperties == null)
{
typedJsonProperties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p =>
(p.CanRead && !p.GetCustomAttributes<JsonIgnoreAttribute>(inherit: true).Any()) &&
(p.CanWrite || p.GetCustomAttributes<JsonPropertyAttribute>(inherit: true).Any())
)
.Select(p =>
{
string name = p.GetCustomAttribute<JsonPropertyAttribute>(inherit: true)?.PropertyName ?? p.Name;
return new InnerTypedJsonProperty
(
propertyName: name,
propertyInfo: p,
isNArrayProperty: PROPERTY_NAME_NARRAY.Equals(name) && p.PropertyType.IsArray && p.PropertyType.GetElementType()!.IsClass
);
})
.ToArray();
_mappedTypeJsonProperties[mappedTypeKey] = typedJsonProperties;
}
return typedJsonProperties;
}
private static bool TryMatchNArrayIndex(string key, out int index)
{
Regex regex = new Regex(@"(_)(\d+)", RegexOptions.Compiled);
if (regex.IsMatch(key))
{
string str = regex.Match(key).Groups[2].Value;
index = int.Parse(str);
return true;
}
index = -1;
return false;
}
private static Array CreateOrExpandNArray(object? array, Type elementType, int capacity)
{
if (elementType == null) throw new ArgumentNullException(nameof(elementType));
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity));
if (array == null)
{
return Array.CreateInstance(elementType, capacity);
}
Array src = (Array)array;
if (src.Length < capacity)
{
Array dst = Array.CreateInstance(elementType, capacity);
Array.Copy(src, dst, src.Length);
return dst;
}
return src;
}
private static object CreateOrUpdateNArrayElement(Array array, int index, string jKey, JToken? jValue, JsonSerializer? serializer = null)
{
if (array == null) throw new ArgumentNullException(nameof(array));
if (index < 0) throw new ArgumentOutOfRangeException(nameof(index));
object? element = array.GetValue(index);
Type elementType = array.GetType().GetElementType()!;
if (element == null)
{
if (elementType.IsAbstract || elementType.IsInterface)
{
throw new NotSupportedException();
}
else if (elementType.IsArray)
{
element = Array.CreateInstance(elementType, 0);
}
else
{
element = Activator.CreateInstance(elementType);
}
array.SetValue(element, index);
}
InnerTypedJsonProperty? typedJsonProperty = GetTypedJsonProperties(elementType)
.SingleOrDefault(p => string.Equals(p.PropertyName.Replace(PROPERTY_WILDCARD_NARRAY_ELEMENT, index.ToString()), jKey));
if (typedJsonProperty != null)
{
serializer = serializer ?? JsonSerializer.CreateDefault();
foreach (JsonConverterAttribute attribute in typedJsonProperty.PropertyInfo.GetCustomAttributes<JsonConverterAttribute>(inherit: true))
{
JsonConverter converter = (JsonConverter)Activator.CreateInstance(attribute.ConverterType, attribute.ConverterParameters)!;
serializer.Converters.Add(converter);
}
object? obj = jValue?.ToObject(typedJsonProperty.PropertyType, serializer);
typedJsonProperty.PropertyInfo.SetValue(element, obj);
}
return element!;
}
}
}

View File

@ -1,13 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace System.Text.Json.Converters
{
internal class StringTypedBooleanConverter : JsonConverter<bool>
internal class YesOrNoBooleanConverter : JsonConverter<bool>
{
private readonly JsonConverter<bool?> _converter = new StringTypedNullableBooleanConverter();
private readonly JsonConverter<bool?> _converter = new YesOrNoNullableBooleanConverter();
public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{

View File

@ -2,7 +2,7 @@
namespace System.Text.Json.Converters
{
internal class StringTypedNullableBooleanConverter : JsonConverter<bool?>
internal class YesOrNoNullableBooleanConverter : JsonConverter<bool?>
{
public override bool? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
@ -18,15 +18,15 @@ namespace System.Text.Json.Converters
{
return false;
}
else if (reader.TokenType == JsonTokenType.String)
else if (reader.TokenType == System.Text.Json.JsonTokenType.String)
{
string? value = reader.GetString();
if (value == null)
return null;
if ("true".Equals(value, StringComparison.OrdinalIgnoreCase))
if ("Y".Equals(value))
return true;
else if ("false".Equals(value, StringComparison.OrdinalIgnoreCase))
else if ("N".Equals(value))
return false;
}
@ -36,7 +36,7 @@ namespace System.Text.Json.Converters
public override void Write(Utf8JsonWriter writer, bool? value, JsonSerializerOptions options)
{
if (value.HasValue)
writer.WriteStringValue(value.Value ? "true" : "false");
writer.WriteStringValue(value.Value ? "Y" : "N");
else
writer.WriteNullValue();
}

View File

@ -0,0 +1,19 @@
using System.Text.Json.Serialization;
namespace System.Text.Json.Converters
{
internal class PureDigitalTextDateTimeOffsetConverter : JsonConverter<DateTimeOffset>
{
private readonly JsonConverter<DateTimeOffset?> _converter = new PureDigitalTextNullableDateTimeOffsetConverter();
public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return _converter.Read(ref reader, typeToConvert, options) ?? default;
}
public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options)
{
_converter.Write(writer, value, options);
}
}
}

View File

@ -0,0 +1,40 @@
using System.Globalization;
using System.Text.Json.Serialization;
namespace System.Text.Json.Converters
{
internal class PureDigitalTextNullableDateTimeOffsetConverter : JsonConverter<DateTimeOffset?>
{
private const string DATETIME_FORMAT = Newtonsoft.Json.Converters.PureDigitalTextNullableDateTimeOffsetConverter.DATETIME_FORMAT;
public override DateTimeOffset? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
{
return null;
}
else if (reader.TokenType == JsonTokenType.String)
{
string? value = reader.GetString();
if (value == null)
return null;
if (DateTimeOffset.TryParseExact(value, DATETIME_FORMAT, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out DateTimeOffset result))
return result;
if (DateTimeOffset.TryParse(value, out result))
return result;
}
throw new JsonException();
}
public override void Write(Utf8JsonWriter writer, DateTimeOffset? value, JsonSerializerOptions options)
{
if (value.HasValue)
writer.WriteStringValue(value.Value.ToString(DATETIME_FORMAT, DateTimeFormatInfo.InvariantInfo));
else
writer.WriteNullValue();
}
}
}

View File

@ -0,0 +1,206 @@
using System.Collections;
using System.Linq;
using System.Reflection;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
namespace System.Text.Json.Converters
{
internal static class FlattenNArrayObjectConverterBase
{
public const string PROPERTY_WILDCARD_NARRAY_ELEMENT = Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_WILDCARD_NARRAY_ELEMENT;
public const string PROPERTY_NAME_NARRAY = Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY;
}
internal class FlattenNArrayObjectConverterBase<T> : JsonConverter<T?>
where T : class, new()
{
private sealed class InnerTypedJsonProperty
{
public string PropertyName { get; }
public PropertyInfo PropertyInfo { get; }
public Type PropertyType { get { return PropertyInfo.PropertyType; } }
public bool IsNArrayProperty { get; }
public InnerTypedJsonProperty(string propertyName, PropertyInfo propertyInfo, bool isNArrayProperty)
{
PropertyName = propertyName;
PropertyInfo = propertyInfo;
IsNArrayProperty = isNArrayProperty;
}
}
private const string PROPERTY_WILDCARD_NARRAY_ELEMENT = FlattenNArrayObjectConverterBase.PROPERTY_WILDCARD_NARRAY_ELEMENT;
private const string PROPERTY_NAME_NARRAY = FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY;
private static readonly Hashtable _mappedTypeJsonProperties = new Hashtable();
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
{
return default;
}
else if (reader.TokenType == JsonTokenType.StartObject)
{
InnerTypedJsonProperty[] typedJsonProperties = GetTypedJsonProperties(typeToConvert);
if (typedJsonProperties.Count(p => p.IsNArrayProperty) != 1)
throw new JsonException("The number of `$n` properties must be only one.");
JsonElement jElement = JsonDocument.ParseValue(ref reader).RootElement.Clone();
T tObject = new T();
foreach (JsonProperty jKey in jElement.EnumerateObject())
{
InnerTypedJsonProperty? typedJsonProperty = typedJsonProperties.SingleOrDefault(e => e.PropertyName == jKey.Name);
if (typedJsonProperty != null)
{
// 处理普通属性
object? value = options is null ?
JsonSerializer.Deserialize(jKey.Value, typedJsonProperty.PropertyType, options) :
JsonSerializer.Deserialize(jKey.Value, typedJsonProperty.PropertyType, options);
typedJsonProperty.PropertyInfo.SetValue(tObject, value);
}
else if (TryMatchNArrayIndex(jKey.Name, out int index))
{
// 处理 $n 属性
InnerTypedJsonProperty narrayJsonProperty = typedJsonProperties.Single(e => e.IsNArrayProperty);
object? value = narrayJsonProperty.PropertyInfo.GetValue(tObject);
Array array = CreateOrExpandNArray(value, narrayJsonProperty.PropertyType.GetElementType()!, index + 1);
object? element = CreateOrUpdateNArrayElement(array, index, jKey.Name, jKey.Value, options);
narrayJsonProperty.PropertyInfo.SetValue(tObject, array);
}
}
return tObject;
}
throw new JsonException();
}
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
{
if (value is null)
{
writer.WriteNullValue();
return;
}
throw new NotImplementedException();
}
private static InnerTypedJsonProperty[] GetTypedJsonProperties(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
string mappedTypeKey = type.AssemblyQualifiedName ?? type.GetHashCode().ToString();
InnerTypedJsonProperty[]? typedJsonProperties = (InnerTypedJsonProperty[]?)_mappedTypeJsonProperties[mappedTypeKey];
if (typedJsonProperties == null)
{
typedJsonProperties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p =>
(p.CanRead && !p.GetCustomAttributes<JsonIgnoreAttribute>(inherit: true).Any()) &&
(p.CanWrite || p.GetCustomAttributes<JsonIncludeAttribute>(inherit: true).Any())
)
.Select(p =>
{
string name = p.GetCustomAttribute<JsonPropertyNameAttribute>(inherit: true)?.Name ?? p.Name;
return new InnerTypedJsonProperty
(
propertyName: name,
propertyInfo: p,
isNArrayProperty: PROPERTY_NAME_NARRAY.Equals(name) && p.PropertyType.IsArray && p.PropertyType.GetElementType()!.IsClass
);
})
.ToArray();
_mappedTypeJsonProperties[mappedTypeKey] = typedJsonProperties;
}
return typedJsonProperties;
}
private static bool TryMatchNArrayIndex(string key, out int index)
{
Regex regex = new Regex(@"(_)(\d+)", RegexOptions.Compiled);
if (regex.IsMatch(key))
{
string str = regex.Match(key).Groups[2].Value;
index = int.Parse(str);
return true;
}
index = -1;
return false;
}
private static Array CreateOrExpandNArray(object? array, Type elementType, int capacity)
{
if (elementType == null) throw new ArgumentNullException(nameof(elementType));
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity));
if (array == null)
{
return Array.CreateInstance(elementType, capacity);
}
Array src = (Array)array;
if (src.Length < capacity)
{
Array dst = Array.CreateInstance(elementType, capacity);
Array.Copy(src, dst, src.Length);
return dst;
}
return src;
}
private static object CreateOrUpdateNArrayElement(Array array, int index, string jKey, JsonElement jValue, JsonSerializerOptions? serializerOptions = null)
{
if (array == null) throw new ArgumentNullException(nameof(array));
if (index < 0) throw new ArgumentOutOfRangeException(nameof(index));
object? element = array.GetValue(index);
Type elementType = array.GetType().GetElementType()!;
if (element == null)
{
if (elementType.IsAbstract || elementType.IsInterface)
{
throw new NotSupportedException();
}
else if (elementType.IsArray)
{
element = Array.CreateInstance(elementType, 0);
}
else
{
element = Activator.CreateInstance(elementType);
}
array.SetValue(element, index);
}
InnerTypedJsonProperty? typedJsonProperty = GetTypedJsonProperties(elementType)
.SingleOrDefault(p => string.Equals(p.PropertyName.Replace(PROPERTY_WILDCARD_NARRAY_ELEMENT, index.ToString()), jKey));
if (typedJsonProperty != null)
{
serializerOptions = (serializerOptions == null) ? new JsonSerializerOptions() : new JsonSerializerOptions(serializerOptions);
foreach (JsonConverterAttribute attribute in typedJsonProperty.PropertyInfo.GetCustomAttributes<JsonConverterAttribute>(inherit: true))
{
JsonConverter converter = (JsonConverter)Activator.CreateInstance(attribute.ConverterType!);
serializerOptions.Converters.Add(converter!);
}
object? obj = JsonSerializer.Deserialize(jValue, typedJsonProperty.PropertyType, serializerOptions)!;
typedJsonProperty.PropertyInfo.SetValue(element, obj);
}
return element!;
}
}
}

View File

@ -0,0 +1,68 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Flurl.Http;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2
{
public static class WechatTenpayClientExecuteMerchantCustomsExtensions
{
/// <summary>
/// <para>异步调用 [POST] /cgi-bin/mch/customs/customdeclareorder 接口。</para>
/// <para>REF: https://pay.weixin.qq.com/wiki/doc/api/external/declarecustom.php?chapter=18_1 </para>
/// </summary>
/// <param name="client"></param>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<Models.CreateMerchantCustomsCustomDeclarationResponse> ExecuteCreateMerchantCustomsCustomDeclarationAsync(this WechatTenpayClient client, Models.CreateMerchantCustomsCustomDeclarationRequest 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, "cgi-bin", "mch", "customs", "customdeclareorder");
return await client.SendRequestWithXmlAsync<Models.CreateMerchantCustomsCustomDeclarationResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
/// <summary>
/// <para>异步调用 [POST] /cgi-bin/mch/customs/customdeclarequery 接口。</para>
/// <para>REF: https://pay.weixin.qq.com/wiki/doc/api/external/declarecustom.php?chapter=18_2 </para>
/// </summary>
/// <param name="client"></param>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<Models.QueryMerchantCustomsCustomDeclarationResponse> ExecuteQueryMerchantCustomsCustomDeclarationAsync(this WechatTenpayClient client, Models.QueryMerchantCustomsCustomDeclarationRequest 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, "cgi-bin", "mch", "customs", "customdeclarequery");
return await client.SendRequestWithXmlAsync<Models.QueryMerchantCustomsCustomDeclarationResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
/// <summary>
/// <para>异步调用 [POST] /cgi-bin/mch/customs/customdeclareredeclare 接口。</para>
/// <para>REF: https://pay.weixin.qq.com/wiki/doc/api/external/declarecustom.php?chapter=18_4&index=3 </para>
/// </summary>
/// <param name="client"></param>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<Models.RedeclareMerchantCustomsCustomDeclarationResponse> ExecuteRedeclareMerchantCustomsCustomDeclarationAsync(this WechatTenpayClient client, Models.RedeclareMerchantCustomsCustomDeclarationRequest 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, "cgi-bin", "mch", "customs", "customdeclareredeclare");
return await client.SendRequestWithXmlAsync<Models.RedeclareMerchantCustomsCustomDeclarationResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
}
}

View File

@ -0,0 +1,32 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Flurl.Http;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2
{
public static class WechatTenpayClientExecutePayExtensions
{
/// <summary>
/// <para>异步调用 [POST] /pay/micropay 接口。</para>
/// <para>REF: https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1 </para>
/// <para>REF: https://pay.weixin.qq.com/wiki/doc/api/micropay_sl.php?chapter=9_10&index=1 </para>
/// <para>REF: https://pay.weixin.qq.com/wiki/doc/api/danpin.php?chapter=9_101&index=1 </para>
/// </summary>
/// <param name="client"></param>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<Models.CreatePayMicroPayResponse> ExecuteCreatePayMicroPayAsync(this WechatTenpayClient client, Models.CreatePayMicroPayRequest 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, "pay", "micropay");
return await client.SendRequestWithXmlAsync<Models.CreatePayMicroPayResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Flurl.Http;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2
{
public static class WechatTenpayClientExecutePayITILExtensions
{
/// <summary>
/// <para>异步调用 [POST] /payitil/report 接口。</para>
/// <para>REF: https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_14&index=8 </para>
/// </summary>
/// <param name="client"></param>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<Models.SubmitPayITILReportResponse> ExecuteSubmitPayITILReportAsync(this WechatTenpayClient client, Models.SubmitPayITILReportRequest 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, "payitil", "report");
return await client.SendRequestWithXmlAsync<Models.SubmitPayITILReportResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Flurl.Http;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2
{
public static class WechatTenpayClientExecuteToolsExtensions
{
/// <summary>
/// <para>异步调用 [POST] /tools/authcodetoopenid 接口。</para>
/// <para>REF: https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_13&index=9 </para>
/// <para>REF: https://pay.weixin.qq.com/wiki/doc/api/micropay_sl.php?chapter=9_12&index=8 </para>
/// </summary>
/// <param name="client"></param>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public static async Task<Models.ToolsAuthCodeToOpenIdResponse> ExecuteToolsAuthCodeToOpenIdAsync(this WechatTenpayClient client, Models.ToolsAuthCodeToOpenIdRequest 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, "tools", "authcodetoopenid");
return await client.SendRequestWithXmlAsync<Models.ToolsAuthCodeToOpenIdResponse>(flurlReq, data: request, cancellationToken: cancellationToken);
}
}
}

View File

@ -0,0 +1,106 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
{
/// <summary>
/// <para>表示 [POST] /cgi-bin/mch/customs/customdeclareorder 接口的请求。</para>
/// </summary>
public class CreateMerchantCustomsCustomDeclarationRequest : WechatTenpaySignableRequest
{
/// <summary>
/// 获取或设置商户订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("out_trade_no")]
[System.Text.Json.Serialization.JsonPropertyName("out_trade_no")]
public string? OutTradeNumber { get; set; }
/// <summary>
/// 获取或设置微信支付订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("transaction_id")]
[System.Text.Json.Serialization.JsonPropertyName("transaction_id")]
public string? TransactionId { get; set; }
/// <summary>
/// 获取或设置商户子订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_order_no")]
[System.Text.Json.Serialization.JsonPropertyName("sub_order_no")]
public string? SubOrderNumber { get; set; }
/// <summary>
/// 获取或设置海关。
/// </summary>
[Newtonsoft.Json.JsonProperty("customs")]
[System.Text.Json.Serialization.JsonPropertyName("customs")]
public string Customs { get; set; } = string.Empty;
/// <summary>
/// 获取或设置商户海关备案号。
/// </summary>
[Newtonsoft.Json.JsonProperty("mch_customs_no")]
[System.Text.Json.Serialization.JsonPropertyName("mch_customs_no")]
public string MerchantCustomsNumber { get; set; } = string.Empty;
/// <summary>
/// 获取或设置关税(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("duty")]
[System.Text.Json.Serialization.JsonPropertyName("duty")]
public int? Duty { get; set; }
/// <summary>
/// 获取或设置报关类型。
/// </summary>
[Newtonsoft.Json.JsonProperty("action_type")]
[System.Text.Json.Serialization.JsonPropertyName("action_type")]
public string? ActionType { get; set; }
/// <summary>
/// 获取或设置币种。
/// </summary>
[Newtonsoft.Json.JsonProperty("fee_type")]
[System.Text.Json.Serialization.JsonPropertyName("fee_type")]
public string? FeeType { get; set; }
/// <summary>
/// 获取或设置应付金额(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("order_fee")]
[System.Text.Json.Serialization.JsonPropertyName("order_fee")]
public int? OrderFee { get; set; }
/// <summary>
/// 获取或设置物流费(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("transport_fee")]
[System.Text.Json.Serialization.JsonPropertyName("transport_fee")]
public int? TransportFee { get; set; }
/// <summary>
/// 获取或设置商品价格(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("product_fee")]
[System.Text.Json.Serialization.JsonPropertyName("product_fee")]
public int? ProductFee { get; set; }
/// <summary>
/// 获取或设置证件类型。
/// </summary>
[Newtonsoft.Json.JsonProperty("cert_type")]
[System.Text.Json.Serialization.JsonPropertyName("cert_type")]
public string? CertificateType { get; set; }
/// <summary>
/// 获取或设置证件号码。
/// </summary>
[Newtonsoft.Json.JsonProperty("cert_id")]
[System.Text.Json.Serialization.JsonPropertyName("cert_id")]
public string? CertificateId { get; set; }
/// <summary>
/// 获取或设置证件姓名。
/// </summary>
[Newtonsoft.Json.JsonProperty("name")]
[System.Text.Json.Serialization.JsonPropertyName("name")]
public string? CertificateName { get; set; }
}
}

View File

@ -0,0 +1,75 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
{
/// <summary>
/// <para>表示 [POST] /cgi-bin/mch/customs/customdeclareorder 接口的响应。</para>
/// </summary>
public class CreateMerchantCustomsCustomDeclarationResponse : WechatTenpaySignableResponse
{
/// <summary>
/// 获取或设置状态码。
/// </summary>
[Newtonsoft.Json.JsonProperty("state")]
[System.Text.Json.Serialization.JsonPropertyName("state")]
public string State { get; set; } = default!;
/// <summary>
/// 获取或设置商户订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("out_trade_no")]
[System.Text.Json.Serialization.JsonPropertyName("out_trade_no")]
public string OutTradeNumber { get; set; } = default!;
/// <summary>
/// 获取或设置微信支付订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("transaction_id")]
[System.Text.Json.Serialization.JsonPropertyName("transaction_id")]
public string TransactionId { get; set; } = default!;
/// <summary>
/// 获取或设置商户子订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_order_no")]
[System.Text.Json.Serialization.JsonPropertyName("sub_order_no")]
public string? SubOrderNumber { get; set; }
/// <summary>
/// 获取或设置微信子订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_order_id")]
[System.Text.Json.Serialization.JsonPropertyName("sub_order_id")]
public string? SubOrderId { get; set; }
/// <summary>
/// 获取或设置最后更新时间。
/// </summary>
[Newtonsoft.Json.JsonProperty("modify_time")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.PureDigitalTextDateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("modify_time")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.PureDigitalTextDateTimeOffsetConverter))]
public DateTimeOffset ModifyTime { get; set; }
/// <summary>
/// 获取或设置订购人和支付人身份信息校验结果。
/// </summary>
[Newtonsoft.Json.JsonProperty("cert_check_result")]
[System.Text.Json.Serialization.JsonPropertyName("cert_check_result")]
public string? CertificateCheckResult { get; set; }
/// <summary>
/// 获取或设置验核机构。
/// </summary>
[Newtonsoft.Json.JsonProperty("verify_department")]
[System.Text.Json.Serialization.JsonPropertyName("verify_department")]
public string? VerifyDepartment { get; set; }
/// <summary>
/// 获取或设置验核机构交易流水号。
/// </summary>
[Newtonsoft.Json.JsonProperty("verify_department_trade_id")]
[System.Text.Json.Serialization.JsonPropertyName("verify_department_trade_id")]
public string? VerifyDepartmentTradeId { get; set; }
}
}

View File

@ -0,0 +1,43 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
{
/// <summary>
/// <para>表示 [POST] /cgi-bin/mch/customs/customdeclarequery 接口的请求。</para>
/// </summary>
public class QueryMerchantCustomsCustomDeclarationRequest : WechatTenpaySignableRequest
{
/// <summary>
/// 获取或设置商户订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("out_trade_no")]
[System.Text.Json.Serialization.JsonPropertyName("out_trade_no")]
public string? OutTradeNumber { get; set; }
/// <summary>
/// 获取或设置微信支付订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("transaction_id")]
[System.Text.Json.Serialization.JsonPropertyName("transaction_id")]
public string? TransactionId { get; set; }
/// <summary>
/// 获取或设置商户子订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_order_no")]
[System.Text.Json.Serialization.JsonPropertyName("sub_order_no")]
public string? SubOrderNumber { get; set; }
/// <summary>
/// 获取或设置微信子订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_order_id")]
[System.Text.Json.Serialization.JsonPropertyName("sub_order_id")]
public string? SubOrderId { get; set; }
/// <summary>
/// 获取或设置海关。
/// </summary>
[Newtonsoft.Json.JsonProperty("customs")]
[System.Text.Json.Serialization.JsonPropertyName("customs")]
public string Customs { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,157 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
{
/// <summary>
/// <para>表示 [POST] /cgi-bin/mch/customs/customdeclarequery 接口的响应。</para>
/// </summary>
[Newtonsoft.Json.JsonConverter(typeof(Converters.ResponseClassNewtonsoftJsonConverter))]
[System.Text.Json.Serialization.JsonConverter(typeof(Converters.ResponseClassSystemTextJsonConverter))]
public class QueryMerchantCustomsCustomDeclarationResponse : WechatTenpaySignableResponse
{
public static class Types
{
public class Record
{
/// <summary>
/// 获取或设置状态码。
/// </summary>
[Newtonsoft.Json.JsonProperty("state_$n")]
[System.Text.Json.Serialization.JsonPropertyName("state_$n")]
public string State { get; set; } = default!;
/// <summary>
/// 获取或设置商户子订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_order_no_$n")]
[System.Text.Json.Serialization.JsonPropertyName("sub_order_no_$n")]
public string? SubOrderNumber { get; set; }
/// <summary>
/// 获取或设置微信子订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_order_id_$n")]
[System.Text.Json.Serialization.JsonPropertyName("sub_order_id_$n")]
public string? SubOrderId { get; set; }
/// <summary>
/// 获取或设置海关。
/// </summary>
[Newtonsoft.Json.JsonProperty("customs_$n")]
[System.Text.Json.Serialization.JsonPropertyName("customs_$n")]
public string Customs { get; set; } = default!;
/// <summary>
/// 获取或设置商户海关备案号。
/// </summary>
[Newtonsoft.Json.JsonProperty("mch_customs_no_$n")]
[System.Text.Json.Serialization.JsonPropertyName("mch_customs_no_$n")]
public string MerchantCustomsNumber { get; set; } = default!;
/// <summary>
/// 获取或设置关税(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("duty_$n")]
[System.Text.Json.Serialization.JsonPropertyName("duty_$n")]
public int? Duty { get; set; }
/// <summary>
/// 获取或设置币种。
/// </summary>
[Newtonsoft.Json.JsonProperty("fee_type_$n")]
[System.Text.Json.Serialization.JsonPropertyName("fee_type_$n")]
public string? FeeType { get; set; }
/// <summary>
/// 获取或设置应付金额(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("order_fee_$n")]
[System.Text.Json.Serialization.JsonPropertyName("order_fee_$n")]
public int? OrderFee { get; set; }
/// <summary>
/// 获取或设置物流费(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("transport_fee_$n")]
[System.Text.Json.Serialization.JsonPropertyName("transport_fee_$n")]
public int? TransportFee { get; set; }
/// <summary>
/// 获取或设置商品价格(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("product_fee_$n")]
[System.Text.Json.Serialization.JsonPropertyName("product_fee_$n")]
public int? ProductFee { get; set; }
/// <summary>
/// 获取或设置最后更新时间。
/// </summary>
[Newtonsoft.Json.JsonProperty("modify_time_$n")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.PureDigitalTextDateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("modify_time_$n")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.PureDigitalTextDateTimeOffsetConverter))]
public DateTimeOffset ModifyTime { get; set; }
/// <summary>
/// 获取或设置订购人和支付人身份信息校验结果。
/// </summary>
[Newtonsoft.Json.JsonProperty("cert_check_result_$n")]
[System.Text.Json.Serialization.JsonPropertyName("cert_check_result_$n")]
public string? CertificateCheckResult { get; set; }
/// <summary>
/// 获取或设置申报结果说明。
/// </summary>
[Newtonsoft.Json.JsonProperty("explanation_$n")]
[System.Text.Json.Serialization.JsonPropertyName("explanation_$n")]
public string? Explanation { get; set; }
}
}
internal static class Converters
{
internal class ResponseClassNewtonsoftJsonConverter : Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase<QueryMerchantCustomsCustomDeclarationResponse>
{
}
internal class ResponseClassSystemTextJsonConverter : System.Text.Json.Converters.FlattenNArrayObjectConverterBase<QueryMerchantCustomsCustomDeclarationResponse>
{
}
}
/// <summary>
/// 获取或设置微信支付订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("transaction_id")]
[System.Text.Json.Serialization.JsonPropertyName("transaction_id")]
public string TransactionId { get; set; } = default!;
/// <summary>
/// 获取或设置记录列表。
/// </summary>
[Newtonsoft.Json.JsonProperty(Newtonsoft.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY)]
[System.Text.Json.Serialization.JsonPropertyName(System.Text.Json.Converters.FlattenNArrayObjectConverterBase.PROPERTY_NAME_NARRAY)]
public Types.Record[] RecordList { get; set; } = default!;
/// <summary>
/// 获取或设置记录总数。
/// </summary>
[Newtonsoft.Json.JsonProperty("count")]
[System.Text.Json.Serialization.JsonPropertyName("count")]
public int RecordCount { get; set; }
/// <summary>
/// 获取或设置验核机构。
/// </summary>
[Newtonsoft.Json.JsonProperty("verify_department")]
[System.Text.Json.Serialization.JsonPropertyName("verify_department")]
public string? VerifyDepartment { get; set; }
/// <summary>
/// 获取或设置验核机构交易流水号。
/// </summary>
[Newtonsoft.Json.JsonProperty("verify_department_trade_id")]
[System.Text.Json.Serialization.JsonPropertyName("verify_department_trade_id")]
public string? VerifyDepartmentTradeId { get; set; }
}
}

View File

@ -0,0 +1,50 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
{
/// <summary>
/// <para>表示 [POST] /cgi-bin/mch/customs/customdeclareredeclare 接口的请求。</para>
/// </summary>
public class RedeclareMerchantCustomsCustomDeclarationRequest : WechatTenpaySignableRequest
{
/// <summary>
/// 获取或设置商户订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("out_trade_no")]
[System.Text.Json.Serialization.JsonPropertyName("out_trade_no")]
public string? OutTradeNumber { get; set; }
/// <summary>
/// 获取或设置微信支付订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("transaction_id")]
[System.Text.Json.Serialization.JsonPropertyName("transaction_id")]
public string? TransactionId { get; set; }
/// <summary>
/// 获取或设置商户子订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_order_no")]
[System.Text.Json.Serialization.JsonPropertyName("sub_order_no")]
public string? SubOrderNumber { get; set; }
/// <summary>
/// 获取或设置微信子订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_order_id")]
[System.Text.Json.Serialization.JsonPropertyName("sub_order_id")]
public string? SubOrderId { get; set; }
/// <summary>
/// 获取或设置海关。
/// </summary>
[Newtonsoft.Json.JsonProperty("customs")]
[System.Text.Json.Serialization.JsonPropertyName("customs")]
public string Customs { get; set; } = string.Empty;
/// <summary>
/// 获取或设置商户海关备案号。
/// </summary>
[Newtonsoft.Json.JsonProperty("mch_customs_no")]
[System.Text.Json.Serialization.JsonPropertyName("mch_customs_no")]
public string MerchantCustomsNumber { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,61 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
{
/// <summary>
/// <para>表示 [POST] /cgi-bin/mch/customs/customdeclareredeclare 接口的响应。</para>
/// </summary>
public class RedeclareMerchantCustomsCustomDeclarationResponse : WechatTenpaySignableResponse
{
/// <summary>
/// 获取或设置状态码。
/// </summary>
[Newtonsoft.Json.JsonProperty("state")]
[System.Text.Json.Serialization.JsonPropertyName("state")]
public string State { get; set; } = default!;
/// <summary>
/// 获取或设置商户订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("out_trade_no")]
[System.Text.Json.Serialization.JsonPropertyName("out_trade_no")]
public string OutTradeNumber { get; set; } = default!;
/// <summary>
/// 获取或设置微信支付订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("transaction_id")]
[System.Text.Json.Serialization.JsonPropertyName("transaction_id")]
public string TransactionId { get; set; } = default!;
/// <summary>
/// 获取或设置商户子订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_order_no")]
[System.Text.Json.Serialization.JsonPropertyName("sub_order_no")]
public string? SubOrderNumber { get; set; }
/// <summary>
/// 获取或设置微信子订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_order_id")]
[System.Text.Json.Serialization.JsonPropertyName("sub_order_id")]
public string? SubOrderId { get; set; }
/// <summary>
/// 获取或设置最后更新时间。
/// </summary>
[Newtonsoft.Json.JsonProperty("modify_time")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.PureDigitalTextDateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("modify_time")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.PureDigitalTextDateTimeOffsetConverter))]
public DateTimeOffset ModifyTime { get; set; }
/// <summary>
/// 获取或设置申报结果说明。
/// </summary>
[Newtonsoft.Json.JsonProperty("explanation")]
[System.Text.Json.Serialization.JsonPropertyName("explanation")]
public string? Explanation { get; set; }
}
}

View File

@ -0,0 +1,288 @@
using System;
using System.Collections.Generic;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
{
/// <summary>
/// <para>表示 [POST] /pay/micropay 接口的请求。</para>
/// </summary>
public class CreatePayMicroPayRequest : WechatTenpaySignableRequest
{
public static class Types
{
public class Detail
{
public static class Types
{
public class GoodsDetail
{
/// <summary>
/// 获取或设置商户侧商品编码。
/// </summary>
[Newtonsoft.Json.JsonProperty("goods_id")]
[System.Text.Json.Serialization.JsonPropertyName("goods_id")]
public string MerchantGoodsId { get; set; } = string.Empty;
/// <summary>
/// 获取或设置微信侧商品编码。
/// </summary>
[Newtonsoft.Json.JsonProperty("wxpay_goods_id")]
[System.Text.Json.Serialization.JsonPropertyName("wxpay_goods_id")]
public string? WechatpayGoodsId { get; set; }
/// <summary>
/// 获取或设置商品分类。
/// </summary>
[Newtonsoft.Json.JsonProperty("goods_category")]
[System.Text.Json.Serialization.JsonPropertyName("goods_category")]
public string? GoodsCategory { get; set; }
/// <summary>
/// 获取或设置商品名称。
/// </summary>
[Newtonsoft.Json.JsonProperty("goods_name")]
[System.Text.Json.Serialization.JsonPropertyName("goods_name")]
public string? GoodsName { get; set; }
/// <summary>
/// 获取或设置商品数量。
/// </summary>
[Newtonsoft.Json.JsonProperty("quantity")]
[System.Text.Json.Serialization.JsonPropertyName("quantity")]
public int Quantity { get; set; }
/// <summary>
/// 获取或设置商品单价(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("price")]
[System.Text.Json.Serialization.JsonPropertyName("price")]
public int Price { get; set; }
/// <summary>
/// 获取或设置商品描述。
/// </summary>
[Newtonsoft.Json.JsonProperty("body")]
[System.Text.Json.Serialization.JsonPropertyName("body")]
public string? Body { get; set; }
}
}
/// <summary>
/// 获取或设置订单原价(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("cost_price")]
[System.Text.Json.Serialization.JsonPropertyName("cost_price")]
public int? CostPrice { get; set; }
/// <summary>
/// 获取或设置商品小票 ID。
/// </summary>
[Newtonsoft.Json.JsonProperty("receipt_id")]
[System.Text.Json.Serialization.JsonPropertyName("receipt_id")]
public string? ReceiptId { get; set; }
/// <summary>
/// 获取或设置单品列表。
/// </summary>
[Newtonsoft.Json.JsonProperty("goods_detail")]
[System.Text.Json.Serialization.JsonPropertyName("goods_detail")]
public List<Types.GoodsDetail>? GoodsList { get; set; }
}
public class Scene
{
/// <summary>
/// 获取或设置门店编号。
/// </summary>
[Newtonsoft.Json.JsonProperty("id")]
[System.Text.Json.Serialization.JsonPropertyName("id")]
public string StoreId { get; set; } = string.Empty;
/// <summary>
/// 获取或设置门店名称。
/// </summary>
[Newtonsoft.Json.JsonProperty("name")]
[System.Text.Json.Serialization.JsonPropertyName("name")]
public string? StoreName { get; set; }
/// <summary>
/// 获取或设置地区编码。
/// </summary>
[Newtonsoft.Json.JsonProperty("area_code")]
[System.Text.Json.Serialization.JsonPropertyName("area_code")]
public string? StoreAreaCode { get; set; }
/// <summary>
/// 获取或设置详细地址。
/// </summary>
[Newtonsoft.Json.JsonProperty("address")]
[System.Text.Json.Serialization.JsonPropertyName("address")]
public string? StoreAddress { get; set; }
}
}
internal static class Converters
{
internal class ResponsePropertyDetailNewtonsoftJsonConverter : Newtonsoft.Json.Converters.TextualObjectInJsonFormatConverterBase<Types.Detail>
{
}
internal class ResponsePropertyDetailSystemTextJsonConverter : System.Text.Json.Converters.TextualObjectInJsonFormatConverterBase<Types.Detail>
{
}
internal class ResponsePropertySceneNewtonsoftJsonConverter : Newtonsoft.Json.Converters.TextualObjectInJsonFormatConverterBase<Types.Scene>
{
}
internal class ResponsePropertySceneSystemTextJsonConverter : System.Text.Json.Converters.TextualObjectInJsonFormatConverterBase<Types.Scene>
{
}
}
/// <summary>
/// 获取或设置接口版本号。
/// </summary>
[Newtonsoft.Json.JsonProperty("version")]
[System.Text.Json.Serialization.JsonPropertyName("version")]
public string? Version { get; set; }
/// <summary>
/// 获取或设置子商户号。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_mch_id")]
[System.Text.Json.Serialization.JsonPropertyName("sub_mch_id")]
public string? SubMerchantId { get; set; }
/// <summary>
/// 获取或设置子商户 AppId。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_appid")]
[System.Text.Json.Serialization.JsonPropertyName("sub_appid")]
public string? SubAppId { get; set; }
/// <summary>
/// 获取或设置商品描述。
/// </summary>
[Newtonsoft.Json.JsonProperty("body")]
[System.Text.Json.Serialization.JsonPropertyName("body")]
public string Body { get; set; } = string.Empty;
/// <summary>
/// 获取或设置商户订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("out_trade_no")]
[System.Text.Json.Serialization.JsonPropertyName("out_trade_no")]
public string OutTradeNumber { get; set; } = string.Empty;
/// <summary>
/// 获取或设置订单金额(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("total_fee")]
[System.Text.Json.Serialization.JsonPropertyName("total_fee")]
public int TotalFee { get; set; }
/// <summary>
/// 获取或设置货币类型。
/// </summary>
[Newtonsoft.Json.JsonProperty("fee_type")]
[System.Text.Json.Serialization.JsonPropertyName("fee_type")]
public string? FeeType { get; set; }
/// <summary>
/// 获取或设置付款码。
/// </summary>
[Newtonsoft.Json.JsonProperty("auth_code")]
[System.Text.Json.Serialization.JsonPropertyName("auth_code")]
public string AuthCode { get; set; } = string.Empty;
/// <summary>
/// 获取或设置附加数据。
/// </summary>
[Newtonsoft.Json.JsonProperty("attach")]
[System.Text.Json.Serialization.JsonPropertyName("attach")]
public string? Attachment { get; set; }
/// <summary>
/// 获取或设置终端设备号。
/// </summary>
[Newtonsoft.Json.JsonProperty("device_info")]
[System.Text.Json.Serialization.JsonPropertyName("device_info")]
public string? DeviceInfo { get; set; }
/// <summary>
/// 获取或设置用户终端 IP。
/// </summary>
[Newtonsoft.Json.JsonProperty("spbill_create_ip")]
[System.Text.Json.Serialization.JsonPropertyName("spbill_create_ip")]
public string ClientIp { get; set; } = string.Empty;
/// <summary>
/// 获取或设置订单优惠标记。
/// </summary>
[Newtonsoft.Json.JsonProperty("goods_tag")]
[System.Text.Json.Serialization.JsonPropertyName("goods_tag")]
public string? GoodsTag { get; set; }
/// <summary>
/// 获取或设置指定付款方式编码。
/// </summary>
[Newtonsoft.Json.JsonProperty("limit_pay")]
[System.Text.Json.Serialization.JsonPropertyName("limit_pay")]
public string? LimitPayCode { get; set; }
/// <summary>
/// 获取或设置交易起始时间。
/// </summary>
[Newtonsoft.Json.JsonProperty("time_start")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.PureDigitalTextNullableDateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("time_start")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.PureDigitalTextNullableDateTimeOffsetConverter))]
public DateTimeOffset? StartTime { get; set; }
/// <summary>
/// 获取或设置交易结束时间。
/// </summary>
[Newtonsoft.Json.JsonProperty("time_expire")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.PureDigitalTextNullableDateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("time_expire")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.PureDigitalTextNullableDateTimeOffsetConverter))]
public DateTimeOffset? ExpireTime { get; set; }
/// <summary>
/// 获取或设置商品信息。
/// </summary>
[Newtonsoft.Json.JsonProperty("detail")]
[Newtonsoft.Json.JsonConverter(typeof(Converters.ResponsePropertyDetailNewtonsoftJsonConverter))]
[System.Text.Json.Serialization.JsonPropertyName("detail")]
[System.Text.Json.Serialization.JsonConverter(typeof(Converters.ResponsePropertyDetailSystemTextJsonConverter))]
public Types.Detail? Detail { get; set; }
/// <summary>
/// 获取或设置场景信息。
/// </summary>
[Newtonsoft.Json.JsonProperty("scene_info")]
[Newtonsoft.Json.JsonConverter(typeof(Converters.ResponsePropertySceneNewtonsoftJsonConverter))]
[System.Text.Json.Serialization.JsonPropertyName("scene_info")]
[System.Text.Json.Serialization.JsonConverter(typeof(Converters.ResponsePropertySceneSystemTextJsonConverter))]
public Types.Scene? Scene { get; set; }
/// <summary>
/// 获取或设置是否分账。
/// </summary>
[Newtonsoft.Json.JsonProperty("profit_sharing")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.YesOrNoNullableBooleanConverter))]
[System.Text.Json.Serialization.JsonPropertyName("profit_sharing")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.YesOrNoNullableBooleanConverter))]
public bool? IsProfitSharing { get; set; }
/// <summary>
/// 获取或设置是否开放电子发票入口。
/// </summary>
[Newtonsoft.Json.JsonProperty("receipt")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.YesOrNoNullableBooleanConverter))]
[System.Text.Json.Serialization.JsonPropertyName("receipt")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.YesOrNoNullableBooleanConverter))]
public bool? IsReceiptOpen { get; set; }
}
}

View File

@ -0,0 +1,270 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
{
/// <summary>
/// <para>表示 [POST] /pay/micropay 接口的响应。</para>
/// </summary>
public class CreatePayMicroPayResponse : WechatTenpaySignableResponse
{
public static class Types
{
public class Promotion
{
public static class Types
{
public class GoodsDetail
{
/// <summary>
/// 获取或设置商品编码。
/// </summary>
[Newtonsoft.Json.JsonProperty("goods_id")]
[System.Text.Json.Serialization.JsonPropertyName("goods_id")]
public string GoodsId { get; set; } = default!;
/// <summary>
/// 获取或设置商品数量。
/// </summary>
[Newtonsoft.Json.JsonProperty("quantity")]
[System.Text.Json.Serialization.JsonPropertyName("quantity")]
public int Quantity { get; set; }
/// <summary>
/// 获取或设置商品单价(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("price")]
[System.Text.Json.Serialization.JsonPropertyName("price")]
public int Price { get; set; }
/// <summary>
/// 获取或设置商品优惠金额(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("discount_amount")]
[System.Text.Json.Serialization.JsonPropertyName("discount_amount")]
public int DiscountAmount { get; set; }
/// <summary>
/// 获取或设置商品备注。
/// </summary>
[Newtonsoft.Json.JsonProperty("goods_remark")]
[System.Text.Json.Serialization.JsonPropertyName("goods_remark")]
public string? GoodsRemark { get; set; }
}
}
/// <summary>
/// 获取或设置券或者立减优惠 ID。
/// </summary>
[Newtonsoft.Json.JsonProperty("promotion_id")]
[System.Text.Json.Serialization.JsonPropertyName("promotion_id")]
public string PromotionId { get; set; } = default!;
/// <summary>
/// 获取或设置优惠名称。
/// </summary>
[Newtonsoft.Json.JsonProperty("name")]
[System.Text.Json.Serialization.JsonPropertyName("name")]
public string? Name { get; set; }
/// <summary>
/// 获取或设置优惠范围。
/// </summary>
[Newtonsoft.Json.JsonProperty("scope")]
[System.Text.Json.Serialization.JsonPropertyName("scope")]
public string? Scope { get; set; }
/// <summary>
/// 获取或设置优惠类型。
/// </summary>
[Newtonsoft.Json.JsonProperty("type")]
[System.Text.Json.Serialization.JsonPropertyName("type")]
public string? Type { get; set; }
/// <summary>
/// 获取或设置优惠券面额(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("amount")]
[System.Text.Json.Serialization.JsonPropertyName("amount")]
public int Amount { get; set; }
/// <summary>
/// 获取或设置活动 ID。
/// </summary>
[Newtonsoft.Json.JsonProperty("activity_id")]
[System.Text.Json.Serialization.JsonPropertyName("activity_id")]
public string? ActivityId { get; set; }
/// <summary>
/// 获取或设置微信出资(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("wxpay_contribute")]
[System.Text.Json.Serialization.JsonPropertyName("wxpay_contribute")]
public int? WechatpayContribute { get; set; }
/// <summary>
/// 获取或设置商户出资(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("merchant_contribute")]
[System.Text.Json.Serialization.JsonPropertyName("merchant_contribute")]
public int? MerchantContribute { get; set; }
/// <summary>
/// 获取或设置其他出资(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("other_contribute")]
[System.Text.Json.Serialization.JsonPropertyName("other_contribute")]
public int? OtherContribute { get; set; }
/// <summary>
/// 获取或设置单品列表。
/// </summary>
[Newtonsoft.Json.JsonProperty("goods_detail")]
[System.Text.Json.Serialization.JsonPropertyName("goods_detail")]
public Types.GoodsDetail[]? GoodsList { get; set; }
}
}
internal static class Converters
{
internal class ResponsePropertyPromotionListNewtonsoftJsonConverter : Newtonsoft.Json.Converters.TextualObjectInJsonFormatConverterBase<Types.Promotion[]>
{
}
internal class ResponsePropertyPromotionListSystemTextJsonConverter : System.Text.Json.Converters.TextualObjectInJsonFormatConverterBase<Types.Promotion[]>
{
}
}
/// <summary>
/// 获取或设置子商户号。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_mch_id")]
[System.Text.Json.Serialization.JsonPropertyName("sub_mch_id")]
public string? SubMerchantId { get; set; }
/// <summary>
/// 获取或设置子商户 AppId。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_appid")]
[System.Text.Json.Serialization.JsonPropertyName("sub_appid")]
public string? SubAppId { get; set; }
/// <summary>
/// 获取或设置终端设备号。
/// </summary>
[Newtonsoft.Json.JsonProperty("device_info")]
[System.Text.Json.Serialization.JsonPropertyName("device_info")]
public string? DeviceInfo { get; set; }
/// <summary>
/// 获取或设置用户唯一标识。
/// </summary>
[Newtonsoft.Json.JsonProperty("openid")]
[System.Text.Json.Serialization.JsonPropertyName("openid")]
public string OpenId { get; set; } = default!;
/// <summary>
/// 获取或设置用户是否订阅该公众号标识。
/// </summary>
[Newtonsoft.Json.JsonProperty("is_subscribe")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.YesOrNoBooleanConverter))]
[System.Text.Json.Serialization.JsonPropertyName("is_subscribe")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.YesOrNoBooleanConverter))]
public bool IsSubscribed { get; set; }
/// <summary>
/// 获取或设置交易类型。
/// </summary>
[Newtonsoft.Json.JsonProperty("trade_type")]
[System.Text.Json.Serialization.JsonPropertyName("trade_type")]
public string TradeType { get; set; } = default!;
/// <summary>
/// 获取或设置付款银行。
/// </summary>
[Newtonsoft.Json.JsonProperty("bank_type")]
[System.Text.Json.Serialization.JsonPropertyName("bank_type")]
public string BankType { get; set; } = default!;
/// <summary>
/// 获取或设置订单金额(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("total_fee")]
[System.Text.Json.Serialization.JsonPropertyName("total_fee")]
public int TotalFee { get; set; }
/// <summary>
/// 获取或设置货币类型。
/// </summary>
[Newtonsoft.Json.JsonProperty("fee_type")]
[System.Text.Json.Serialization.JsonPropertyName("fee_type")]
public string? FeeType { get; set; }
/// <summary>
/// 获取或设置应结订单金额(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("settlement_total_fee")]
[System.Text.Json.Serialization.JsonPropertyName("settlement_total_fee")]
public int? SettlementFee { get; set; }
/// <summary>
/// 获取或设置代金券金额。
/// </summary>
[Newtonsoft.Json.JsonProperty("coupon_fee")]
[System.Text.Json.Serialization.JsonPropertyName("coupon_fee")]
public int? FouponFee { get; set; }
/// <summary>
/// 获取或设置现金支付金额(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("cash_fee")]
[System.Text.Json.Serialization.JsonPropertyName("cash_fee")]
public int CashFee { get; set; }
/// <summary>
/// 获取或设置现金支付货币类型。
/// </summary>
[Newtonsoft.Json.JsonProperty("cash_fee_type")]
[System.Text.Json.Serialization.JsonPropertyName("cash_fee_type")]
public string? CashFeeType { get; set; }
/// <summary>
/// 获取或设置商户订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("out_trade_no")]
[System.Text.Json.Serialization.JsonPropertyName("out_trade_no")]
public string OutTradeNumber { get; set; } = default!;
/// <summary>
/// 获取或设置微信支付订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("transaction_id")]
[System.Text.Json.Serialization.JsonPropertyName("transaction_id")]
public string TransactionId { get; set; } = default!;
/// <summary>
/// 获取或设置附加数据。
/// </summary>
[Newtonsoft.Json.JsonProperty("attach")]
[System.Text.Json.Serialization.JsonPropertyName("attach")]
public string? Attachment { get; set; }
/// <summary>
/// 获取或设置支付完成时间。
/// </summary>
[Newtonsoft.Json.JsonProperty("time_end")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.PureDigitalTextNullableDateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("time_end")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.PureDigitalTextNullableDateTimeOffsetConverter))]
public DateTimeOffset? EndTime { get; set; }
/// <summary>
/// 获取或设置优惠信息。
/// </summary>
[Newtonsoft.Json.JsonProperty("promotion_detail")]
[Newtonsoft.Json.JsonConverter(typeof(Converters.ResponsePropertyPromotionListNewtonsoftJsonConverter))]
[System.Text.Json.Serialization.JsonPropertyName("promotion_detail")]
[System.Text.Json.Serialization.JsonConverter(typeof(Converters.ResponsePropertyPromotionListSystemTextJsonConverter))]
public Types.Promotion[]? PromotionList { get; set; }
}
}

View File

@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
{
/// <summary>
/// <para>表示 [POST] /payitil/report 接口的请求。</para>
/// </summary>
public class SubmitPayITILReportRequest : WechatTenpaySignableRequest
{
public static class Types
{
public class Trade
{
/// <summary>
/// 获取或设置商户订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("out_trade_no")]
[System.Text.Json.Serialization.JsonPropertyName("out_trade_no")]
public string OutTradeNumber { get; set; } = string.Empty;
/// <summary>
/// 获取或设置交易开始时间。
/// </summary>
[Newtonsoft.Json.JsonProperty("begin_time")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.PureDigitalTextNullableDateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("begin_time")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.PureDigitalTextNullableDateTimeOffsetConverter))]
public DateTimeOffset? BeginTime { get; set; }
/// <summary>
/// 获取或设置交易完成时间。
/// </summary>
[Newtonsoft.Json.JsonProperty("end_time")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.PureDigitalTextNullableDateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("end_time")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.PureDigitalTextNullableDateTimeOffsetConverter))]
public DateTimeOffset? EndTime { get; set; }
/// <summary>
/// 获取或设置交易状态。
/// </summary>
[Newtonsoft.Json.JsonProperty("state")]
[System.Text.Json.Serialization.JsonPropertyName("state")]
public string? State { get; set; }
/// <summary>
/// 获取或设置错误描述信息。
/// </summary>
[Newtonsoft.Json.JsonProperty("err_msg")]
[System.Text.Json.Serialization.JsonPropertyName("err_msg")]
public string? ErrorMessage { get; set; }
}
}
internal static class Converters
{
internal class ResponsePropertyTradeListNewtonsoftJsonConverter : Newtonsoft.Json.Converters.TextualObjectInJsonFormatConverterBase<IList<Types.Trade>>
{
}
internal class ResponsePropertyTradeListSystemTextJsonConverter : System.Text.Json.Converters.TextualObjectInJsonFormatConverterBase<IList<Types.Trade>>
{
}
}
/// <summary>
/// 获取或设置终端设备号。
/// </summary>
[Newtonsoft.Json.JsonProperty("device_info")]
[System.Text.Json.Serialization.JsonPropertyName("device_info")]
public string? DeviceInfo { get; set; }
/// <summary>
/// 获取或设置访问接口 IP。
/// </summary>
[Newtonsoft.Json.JsonProperty("user_ip")]
[System.Text.Json.Serialization.JsonPropertyName("user_ip")]
public string UserIp { get; set; } = string.Empty;
/// <summary>
/// 获取或设置订单优惠标记。
/// </summary>
[Newtonsoft.Json.JsonProperty("interface_url")]
[System.Text.Json.Serialization.JsonPropertyName("interface_url")]
public string InterfaceUrl { get; set; } = string.Empty;
/// <summary>
/// 获取或设置上报交易数据列表。
/// </summary>
[Newtonsoft.Json.JsonProperty("trades")]
[Newtonsoft.Json.JsonConverter(typeof(Converters.ResponsePropertyTradeListNewtonsoftJsonConverter))]
[System.Text.Json.Serialization.JsonPropertyName("trades")]
[System.Text.Json.Serialization.JsonConverter(typeof(Converters.ResponsePropertyTradeListSystemTextJsonConverter))]
public IList<Types.Trade> TradeList { get; set; } = new List<Types.Trade>();
}
}

View File

@ -0,0 +1,270 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
{
/// <summary>
/// <para>表示 [POST] /payitil/report 接口的响应。</para>
/// </summary>
public class SubmitPayITILReportResponse : WechatTenpaySignableResponse
{
public static class Types
{
public class Promotion
{
public static class Types
{
public class GoodsDetail
{
/// <summary>
/// 获取或设置商品编码。
/// </summary>
[Newtonsoft.Json.JsonProperty("goods_id")]
[System.Text.Json.Serialization.JsonPropertyName("goods_id")]
public string GoodsId { get; set; } = default!;
/// <summary>
/// 获取或设置商品数量。
/// </summary>
[Newtonsoft.Json.JsonProperty("quantity")]
[System.Text.Json.Serialization.JsonPropertyName("quantity")]
public int Quantity { get; set; }
/// <summary>
/// 获取或设置商品单价(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("price")]
[System.Text.Json.Serialization.JsonPropertyName("price")]
public int Price { get; set; }
/// <summary>
/// 获取或设置商品优惠金额(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("discount_amount")]
[System.Text.Json.Serialization.JsonPropertyName("discount_amount")]
public int DiscountAmount { get; set; }
/// <summary>
/// 获取或设置商品备注。
/// </summary>
[Newtonsoft.Json.JsonProperty("goods_remark")]
[System.Text.Json.Serialization.JsonPropertyName("goods_remark")]
public string? GoodsRemark { get; set; }
}
}
/// <summary>
/// 获取或设置券或者立减优惠 ID。
/// </summary>
[Newtonsoft.Json.JsonProperty("promotion_id")]
[System.Text.Json.Serialization.JsonPropertyName("promotion_id")]
public string PromotionId { get; set; } = default!;
/// <summary>
/// 获取或设置优惠名称。
/// </summary>
[Newtonsoft.Json.JsonProperty("name")]
[System.Text.Json.Serialization.JsonPropertyName("name")]
public string? Name { get; set; }
/// <summary>
/// 获取或设置优惠范围。
/// </summary>
[Newtonsoft.Json.JsonProperty("scope")]
[System.Text.Json.Serialization.JsonPropertyName("scope")]
public string? Scope { get; set; }
/// <summary>
/// 获取或设置优惠类型。
/// </summary>
[Newtonsoft.Json.JsonProperty("type")]
[System.Text.Json.Serialization.JsonPropertyName("type")]
public string? Type { get; set; }
/// <summary>
/// 获取或设置优惠券面额(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("amount")]
[System.Text.Json.Serialization.JsonPropertyName("amount")]
public int Amount { get; set; }
/// <summary>
/// 获取或设置活动 ID。
/// </summary>
[Newtonsoft.Json.JsonProperty("activity_id")]
[System.Text.Json.Serialization.JsonPropertyName("activity_id")]
public string? ActivityId { get; set; }
/// <summary>
/// 获取或设置微信出资(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("wxpay_contribute")]
[System.Text.Json.Serialization.JsonPropertyName("wxpay_contribute")]
public int? WechatpayContribute { get; set; }
/// <summary>
/// 获取或设置商户出资(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("merchant_contribute")]
[System.Text.Json.Serialization.JsonPropertyName("merchant_contribute")]
public int? MerchantContribute { get; set; }
/// <summary>
/// 获取或设置其他出资(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("other_contribute")]
[System.Text.Json.Serialization.JsonPropertyName("other_contribute")]
public int? OtherContribute { get; set; }
/// <summary>
/// 获取或设置单品列表。
/// </summary>
[Newtonsoft.Json.JsonProperty("goods_detail")]
[System.Text.Json.Serialization.JsonPropertyName("goods_detail")]
public Types.GoodsDetail[]? GoodsList { get; set; }
}
}
internal static class Converters
{
internal class ResponsePropertyPromotionListNewtonsoftJsonConverter : Newtonsoft.Json.Converters.TextualObjectInJsonFormatConverterBase<Types.Promotion[]>
{
}
internal class ResponsePropertyPromotionListSystemTextJsonConverter : System.Text.Json.Converters.TextualObjectInJsonFormatConverterBase<Types.Promotion[]>
{
}
}
/// <summary>
/// 获取或设置子商户号。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_mch_id")]
[System.Text.Json.Serialization.JsonPropertyName("sub_mch_id")]
public string? SubMerchantId { get; set; }
/// <summary>
/// 获取或设置子商户 AppId。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_appid")]
[System.Text.Json.Serialization.JsonPropertyName("sub_appid")]
public string? SubAppId { get; set; }
/// <summary>
/// 获取或设置终端设备号。
/// </summary>
[Newtonsoft.Json.JsonProperty("device_info")]
[System.Text.Json.Serialization.JsonPropertyName("device_info")]
public string? DeviceInfo { get; set; }
/// <summary>
/// 获取或设置用户唯一标识。
/// </summary>
[Newtonsoft.Json.JsonProperty("openid")]
[System.Text.Json.Serialization.JsonPropertyName("openid")]
public string OpenId { get; set; } = default!;
/// <summary>
/// 获取或设置用户是否订阅该公众号标识。
/// </summary>
[Newtonsoft.Json.JsonProperty("is_subscribe")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.YesOrNoBooleanConverter))]
[System.Text.Json.Serialization.JsonPropertyName("is_subscribe")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.YesOrNoBooleanConverter))]
public bool IsSubscribed { get; set; }
/// <summary>
/// 获取或设置交易类型。
/// </summary>
[Newtonsoft.Json.JsonProperty("trade_type")]
[System.Text.Json.Serialization.JsonPropertyName("trade_type")]
public string TradeType { get; set; } = default!;
/// <summary>
/// 获取或设置付款银行。
/// </summary>
[Newtonsoft.Json.JsonProperty("bank_type")]
[System.Text.Json.Serialization.JsonPropertyName("bank_type")]
public string BankType { get; set; } = default!;
/// <summary>
/// 获取或设置订单金额(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("total_fee")]
[System.Text.Json.Serialization.JsonPropertyName("total_fee")]
public int TotalFee { get; set; }
/// <summary>
/// 获取或设置货币类型。
/// </summary>
[Newtonsoft.Json.JsonProperty("fee_type")]
[System.Text.Json.Serialization.JsonPropertyName("fee_type")]
public string? FeeType { get; set; }
/// <summary>
/// 获取或设置应结订单金额(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("settlement_total_fee")]
[System.Text.Json.Serialization.JsonPropertyName("settlement_total_fee")]
public int? SettlementFee { get; set; }
/// <summary>
/// 获取或设置代金券金额。
/// </summary>
[Newtonsoft.Json.JsonProperty("coupon_fee")]
[System.Text.Json.Serialization.JsonPropertyName("coupon_fee")]
public int? FouponFee { get; set; }
/// <summary>
/// 获取或设置现金支付金额(单位:分)。
/// </summary>
[Newtonsoft.Json.JsonProperty("cash_fee")]
[System.Text.Json.Serialization.JsonPropertyName("cash_fee")]
public int CashFee { get; set; }
/// <summary>
/// 获取或设置现金支付货币类型。
/// </summary>
[Newtonsoft.Json.JsonProperty("cash_fee_type")]
[System.Text.Json.Serialization.JsonPropertyName("cash_fee_type")]
public string? CashFeeType { get; set; }
/// <summary>
/// 获取或设置商户订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("out_trade_no")]
[System.Text.Json.Serialization.JsonPropertyName("out_trade_no")]
public string OutTradeNumber { get; set; } = default!;
/// <summary>
/// 获取或设置微信支付订单号。
/// </summary>
[Newtonsoft.Json.JsonProperty("transaction_id")]
[System.Text.Json.Serialization.JsonPropertyName("transaction_id")]
public string TransactionId { get; set; } = default!;
/// <summary>
/// 获取或设置附加数据。
/// </summary>
[Newtonsoft.Json.JsonProperty("attach")]
[System.Text.Json.Serialization.JsonPropertyName("attach")]
public string? Attachment { get; set; }
/// <summary>
/// 获取或设置支付完成时间。
/// </summary>
[Newtonsoft.Json.JsonProperty("time_end")]
[Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.PureDigitalTextNullableDateTimeOffsetConverter))]
[System.Text.Json.Serialization.JsonPropertyName("time_end")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.PureDigitalTextNullableDateTimeOffsetConverter))]
public DateTimeOffset? EndTime { get; set; }
/// <summary>
/// 获取或设置优惠信息。
/// </summary>
[Newtonsoft.Json.JsonProperty("promotion_detail")]
[Newtonsoft.Json.JsonConverter(typeof(Converters.ResponsePropertyPromotionListNewtonsoftJsonConverter))]
[System.Text.Json.Serialization.JsonPropertyName("promotion_detail")]
[System.Text.Json.Serialization.JsonConverter(typeof(Converters.ResponsePropertyPromotionListSystemTextJsonConverter))]
public Types.Promotion[]? PromotionList { get; set; }
}
}

View File

@ -0,0 +1,29 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
{
/// <summary>
/// <para>表示 [POST] /tools/authcodetoopenid 接口的请求。</para>
/// </summary>
public class ToolsAuthCodeToOpenIdRequest : WechatTenpaySignableRequest
{
/// <summary>
/// 获取或设置子商户号。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_mch_id")]
[System.Text.Json.Serialization.JsonPropertyName("sub_mch_id")]
public string? SubMerchantId { get; set; }
/// <summary>
/// 获取或设置子商户 AppId。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_appid")]
[System.Text.Json.Serialization.JsonPropertyName("sub_appid")]
public string? SubAppId { get; set; }
/// <summary>
/// 获取或设置付款码。
/// </summary>
[Newtonsoft.Json.JsonProperty("auth_code")]
[System.Text.Json.Serialization.JsonPropertyName("auth_code")]
public string AuthCode { get; set; } = string.Empty;
}
}

View File

@ -0,0 +1,36 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Models
{
/// <summary>
/// <para>表示 [POST] /tools/authcodetoopenid 接口的响应。</para>
/// </summary>
public class ToolsAuthCodeToOpenIdResponse : WechatTenpaySignableResponse
{
/// <summary>
/// 获取或设置子商户号。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_mch_id")]
[System.Text.Json.Serialization.JsonPropertyName("sub_mch_id")]
public string? SubMerchantId { get; set; }
/// <summary>
/// 获取或设置子商户 AppId。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_appid")]
[System.Text.Json.Serialization.JsonPropertyName("sub_appid")]
public string? SubAppId { get; set; }
/// <summary>
/// 获取或设置用户唯一标识。
/// </summary>
[Newtonsoft.Json.JsonProperty("openid")]
[System.Text.Json.Serialization.JsonPropertyName("openid")]
public string OpenId { get; set; } = default!;
/// <summary>
/// 获取或设置用户在子商户下的唯一标识。
/// </summary>
[Newtonsoft.Json.JsonProperty("sub_openid")]
[System.Text.Json.Serialization.JsonPropertyName("sub_openid")]
public string? SubOpenId { get; set; }
}
}

View File

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

View File

@ -0,0 +1,31 @@
## SKIT.FlurlHttpClient.Wechat.TenpayV2
[![GitHub Stars](https://img.shields.io/github/stars/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat?logo=github&label=Stars)](https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat)
[![GitHub Forks](https://img.shields.io/github/forks/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat?logo=github&label=Forks)](https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat)
[![NuGet Download](https://img.shields.io/nuget/dt/SKIT.FlurlHttpClient.Wechat.TenpayV2.svg?sanitize=true&label=Downloads)](https://www.nuget.org/packages/SKIT.FlurlHttpClient.Wechat.TenpayV2)
[![License](https://img.shields.io/github/license/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat?label=License)](https://mit-license.org/)
基于 `Flurl.Http` 的微信商户平台 API v2 版客户端。
**注意**:本库仅仅包含微信支付未提供 v3 版 API 的部分功能,如需微信支付 v3 版 API 客户端,欢迎使用 [`SKIT.FlurlHttpClient.Wechat.TenpayV3`](https://www.nuget.org/packages/SKIT.FlurlHttpClient.Wechat.TenpayV3)。
---
### 【功能特性】
- 基于微信支付 v2 版 API 封装。
- 支持直连商户、服务商两种模式。
- 请求时自动生成签名,无需开发者手动干预。
- 提供了微信支付所需的 MD5、HMAC-SHA-256 等算法工具类。
---
### 【开发文档】
[点此查看](https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat)。
---
### 【更新日志】
[点此查看](https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat/blob/main/CHANGELOG.md)。

View File

@ -0,0 +1,47 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net47; netstandard2.0; net5.0; net6.0</TargetFrameworks>
<LangVersion>8.0</LangVersion>
<Nullable>enable</Nullable>
<NullableReferenceTypes>true</NullableReferenceTypes>
</PropertyGroup>
<PropertyGroup>
<PackageId>SKIT.FlurlHttpClient.Wechat.TenpayV2</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 Wechat Weixin MicroMessage Tenpay WechatPay WeixinPay Wxpay 微信 微信支付 微信商户</PackageTags>
<Version>1.0.0-beta</Version>
<Description>基于 Flurl.Http 的微信支付 API v2 版客户端,支持直连商户、服务商模式,仅包含微信支付未提供 v3 版 API 的部分功能。如需微信支付 v3 版 API 客户端,欢迎使用 `SKIT.FlurlHttpClient.Wechat.TenpayV3`。</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" Pack="true" PackagePath="/" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Net.Http.WebRequest" Condition="'$(TargetFramework)' == 'net461' Or '$(TargetFramework)' == 'net47'" />
<Reference Include="System.Web" Condition="'$(TargetFramework)' == 'net461' Or '$(TargetFramework)' == 'net47'" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SKIT.FlurlHttpClient.Common" Version="2.2.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,43 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Settings
{
public class Credentials
{
/// <summary>
/// 初始化客户端时 <see cref="WechatTenpayClientOptions.MerchantId"/> 的副本。
/// </summary>
public string MerchantId { get; }
/// <summary>
/// 初始化客户端时 <see cref="WechatTenpayClientOptions.MerchantSecret"/> 的副本。
/// </summary>
public string MerchantSecret { get; }
/// <summary>
/// 初始化客户端时 <see cref="WechatTenpayClientOptions.MerchantCertificateBytes"/> 的副本。
/// </summary>
public byte[]? MerchantCertificateBytes { get; set; }
/// <summary>
/// 初始化客户端时 <see cref="WechatTenpayClientOptions.MerchantCertificatePassword"/> 的副本。
/// </summary>
public string? MerchantCertificatePassword { get; set; }
/// <summary>
/// 初始化客户端时 <see cref="WechatTenpayClientOptions.AppId"/> 的副本。
/// </summary>
public string? AppId { get; }
internal Credentials(WechatTenpayClientOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
MerchantId = options.MerchantId;
MerchantSecret = options.MerchantSecret;
MerchantCertificateBytes = options.MerchantCertificateBytes;
MerchantCertificatePassword = options.MerchantCertificatePassword;
AppId = options.AppId;
}
}
}

View File

@ -0,0 +1,37 @@
using System.Net.Http;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Settings
{
public class HttpClientFactory : Flurl.Http.Configuration.DefaultHttpClientFactory
{
private readonly byte[]? _certBytes;
private readonly string? _certPassword;
public HttpClientFactory(byte[]? certBytes, string? certPassword)
{
_certBytes = certBytes;
_certPassword = certPassword;
}
public override HttpMessageHandler CreateMessageHandler()
{
#if NETFRAMEWORK
WebRequestHandler handler = new WebRequestHandler();
handler.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => sslPolicyErrors == SslPolicyErrors.None;
#else
HttpClientHandler handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (requestMessage, certificate, chain, sslPolicyErrors) => sslPolicyErrors == SslPolicyErrors.None;
#endif
if (_certBytes != null)
{
X509Certificate x509 = (_certPassword == null) ? new X509Certificate2(_certBytes) : new X509Certificate2(_certBytes, _certPassword);
handler.ClientCertificates.Add(x509);
}
return handler;
}
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.Security.Cryptography;
using System.Text;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Utilities
{
/// <summary>
/// HMAC 算法工具类。
/// </summary>
public static class HMACUtility
{
/// <summary>
/// 获取 HMAC-SHA-256 消息认证码。
/// </summary>
/// <param name="secretBytes">密钥字节数组。</param>
/// <param name="bytes">信息字节数组。</param>
/// <returns>信息摘要。</returns>
public static string HashWithSHA256(byte[] secretBytes, byte[] bytes)
{
if (secretBytes == null) throw new ArgumentNullException(nameof(secretBytes));
if (bytes == null) throw new ArgumentNullException(nameof(bytes));
using HMAC hmac = new HMACSHA256(secretBytes);
byte[] hashBytes = hmac.ComputeHash(bytes);
return BitConverter.ToString(hashBytes).Replace("-", "");
}
/// <summary>
/// 获取 HMAC-SHA-256 消息认证码。
/// </summary>
/// <param name="secret">密钥。</param>
/// <param name="message">文本信息。</param>
/// <returns>信息摘要。</returns>
public static string HashWithSHA256(string secret, string message)
{
if (secret == null) throw new ArgumentNullException(nameof(secret));
if (message == null) throw new ArgumentNullException(nameof(message));
byte[] secretBytes = Encoding.UTF8.GetBytes(secret);
byte[] bytes = Encoding.UTF8.GetBytes(message);
return HashWithSHA256(secretBytes, bytes);
}
}
}

View File

@ -0,0 +1,431 @@
using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Utilities
{
[Obsolete]
internal static partial class JsonUtility
{
public const string PROPERTY_WILDCARD_NARRAY_ELEMENT = "$n";
public const string PROPERTY_NAME_NARRAY = "#n";
private static bool TryMatchNArrayIndex(string key, out int index)
{
Regex regex = new Regex(@"(_)(\d+)", RegexOptions.Compiled);
if (regex.IsMatch(key))
{
string str = regex.Match(key).Groups[2].Value;
index = int.Parse(str);
return true;
}
index = -1;
return false;
}
private static Array CreateOrExpandNArray(object? array, Type elementType, int capacity)
{
if (elementType == null) throw new ArgumentNullException(nameof(elementType));
if (capacity <= 0) throw new ArgumentOutOfRangeException(nameof(capacity));
if (array == null)
{
return Array.CreateInstance(elementType, capacity);
}
Array src = (Array)array;
if (src.Length < capacity)
{
Array dst = Array.CreateInstance(elementType, capacity);
Array.Copy(src, dst, src.Length);
return dst;
}
return src;
}
private static object CreateOrUpdateNArrayElement(Array array, Type elementType, int index, string key, object value, params object?[]? args)
{
if (array == null) throw new ArgumentNullException(nameof(array));
if (elementType == null) throw new ArgumentNullException(nameof(elementType));
static object AppendNArrayElement(Array array, Type elementType, int index)
{
object? element = array.GetValue(index);
if (element == null)
{
if (elementType.IsAbstract || elementType.IsInterface)
{
throw new NotSupportedException();
}
else if (elementType.IsArray)
{
element = Array.CreateInstance(elementType, 0);
}
else
{
element = Activator.CreateInstance(elementType);
}
}
array.SetValue(element, index);
return element!;
}
object? element = AppendNArrayElement(array, elementType, index);
if (value is Newtonsoft.Json.Linq.JToken jToken)
{
var props = GetTypedNewtonsoftJsonProperties(elementType);
var prop = props.SingleOrDefault(p => string.Equals(p.PropertyName.Replace(PROPERTY_WILDCARD_NARRAY_ELEMENT, index.ToString()), key));
if (prop != null)
{
Newtonsoft.Json.JsonSerializer? serializer = args?.FirstOrDefault() as Newtonsoft.Json.JsonSerializer;
if (serializer == null)
{
serializer = Newtonsoft.Json.JsonSerializer.CreateDefault();
}
foreach (Newtonsoft.Json.JsonConverterAttribute attribute in prop.PropertyInfo.GetCustomAttributes<Newtonsoft.Json.JsonConverterAttribute>(inherit: true))
{
Newtonsoft.Json.JsonConverter converter = (Newtonsoft.Json.JsonConverter)Activator.CreateInstance(attribute.ConverterType, attribute.ConverterParameters);
serializer.Converters.Add(converter);
}
object tmp = jToken.ToObject(prop.PropertyType, serializer);
prop.PropertyInfo.SetValue(element, tmp);
}
}
else if (value is System.Text.Json.JsonElement jElement)
{
var props = GetTypedSystemTextJsonProperties(elementType);
var prop = props.SingleOrDefault(p => string.Equals(p.PropertyName.Replace(PROPERTY_WILDCARD_NARRAY_ELEMENT, index.ToString()), key));
if (prop != null)
{
System.Text.Json.JsonSerializerOptions? options = (args?.FirstOrDefault() as System.Text.Json.JsonSerializerOptions);
if (options == null)
{
options = new System.Text.Json.JsonSerializerOptions();
}
else
{
options = new System.Text.Json.JsonSerializerOptions(options);
}
foreach (System.Text.Json.Serialization.JsonConverterAttribute attribute in prop.PropertyInfo.GetCustomAttributes<System.Text.Json.Serialization.JsonConverterAttribute>(inherit: true))
{
System.Text.Json.Serialization.JsonConverter converter = (System.Text.Json.Serialization.JsonConverter)Activator.CreateInstance(attribute.ConverterType!);
options.Converters.Add(converter!);
}
object tmp = System.Text.Json.JsonSerializer.Deserialize(jElement, prop.PropertyType, options)!;
prop.PropertyInfo.SetValue(element, tmp);
}
}
else
{
throw new NotSupportedException();
}
return element;
}
public static string SerializeWhenHasNArray<T>(T obj, Newtonsoft.Json.JsonSerializer? serializer = null)
where T : class, new()
{
if (obj == null) throw new ArgumentNullException(nameof(obj));
static Newtonsoft.Json.Linq.JToken Flatten(Newtonsoft.Json.Linq.JToken jToken)
{
if (jToken.Type == Newtonsoft.Json.Linq.JTokenType.Array)
{
foreach (Newtonsoft.Json.Linq.JToken? jSubToken in jToken)
{
if (jSubToken == null)
continue;
Flatten(jSubToken);
}
}
else if (jToken.Type == Newtonsoft.Json.Linq.JTokenType.Object)
{
string[] keys = ((Newtonsoft.Json.Linq.JObject)jToken).Properties().Select(p => p.Name).ToArray();
foreach (string key in keys)
{
if (!PROPERTY_NAME_NARRAY.Equals(key))
continue;
int i = 0;
foreach (Newtonsoft.Json.Linq.JToken? jSubToken in jToken[key])
{
if (jSubToken == null)
continue;
foreach (Newtonsoft.Json.Linq.JProperty jSubKey in jSubToken)
{
jToken[jSubKey.Name.Replace(PROPERTY_WILDCARD_NARRAY_ELEMENT, i.ToString())] = jSubKey.Value;
}
i++;
}
}
jToken[PROPERTY_NAME_NARRAY]?.Parent?.Remove();
}
return jToken;
}
//StringBuilder stringBuilder = new StringBuilder();
//using TextWriter stringWriter = new StringWriter(stringBuilder);
//serializer = serializer ?? Newtonsoft.Json.JsonSerializer.CreateDefault();
//serializer.Serialize(stringWriter, obj, typeof(T));
//string rawJson = stringBuilder.ToString();
//Newtonsoft.Json.Linq.JToken jToken = Newtonsoft.Json.Linq.JToken.Parse(rawJson);
//return Flatten(jToken).ToString(serializer.Formatting);
// TODO
return default!;
}
public static T DeserializeWhenHasNArray<T>(ref Newtonsoft.Json.Linq.JObject jObject, Newtonsoft.Json.JsonSerializer? serializer = null)
where T : class, new()
{
var props = GetTypedNewtonsoftJsonProperties(typeof(T));
if (props.Count(p => p.IsNArrayProperty) != 1)
throw new Newtonsoft.Json.JsonException("The number of `$n` properties must be only one.");
T result = new T();
foreach (Newtonsoft.Json.Linq.JProperty jKey in jObject.Properties())
{
var prop = props.SingleOrDefault(e => e.PropertyName == jKey.Name);
if (prop != null)
{
// 处理普通属性
object? value = serializer is null ?
jObject[prop.PropertyName]?.ToObject(prop.PropertyType) :
jObject[prop.PropertyName]?.ToObject(prop.PropertyType, serializer);
prop.PropertyInfo.SetValue(result, value);
}
else if (TryMatchNArrayIndex(jKey.Name, out int index))
{
// 处理 $n 属性
var narrProp = props.Single(e => e.IsNArrayProperty);
object? value = narrProp.PropertyInfo.GetValue(result);
Array array = CreateOrExpandNArray(value, narrProp.PropertyType.GetElementType()!, index + 1);
object? element = CreateOrUpdateNArrayElement(array, narrProp.PropertyType.GetElementType()!, index, jKey.Name, jKey.Value, serializer);
narrProp.PropertyInfo.SetValue(result, array);
}
else if (serializer?.MissingMemberHandling == Newtonsoft.Json.MissingMemberHandling.Error)
{
throw new Newtonsoft.Json.JsonSerializationException($"Could not find member `{jKey.Name}` on object of type `{typeof(T).Name}`.");
}
}
return result;
}
public static string SerializeWhenHasNArray<T>(T obj, System.Text.Json.JsonSerializerOptions? options = null)
where T : class, new()
{
if (obj == null) throw new ArgumentNullException(nameof(obj));
if (obj == null) throw new ArgumentNullException(nameof(obj));
static System.Text.Json.Nodes.JsonNode Flatten(
System.Text.Json.Nodes.JsonNode jNode,
System.Text.Json.JsonSerializerOptions jsonSerializerOptions,
System.Text.Json.JsonDocumentOptions jsonDocumentOptions,
System.Text.Json.Nodes.JsonNodeOptions jsonNodeOptions)
{
if (jNode is System.Text.Json.Nodes.JsonArray jNodeAsArray)
{
foreach (System.Text.Json.Nodes.JsonNode? jSubNode in jNodeAsArray)
{
if (jSubNode == null)
continue;
Flatten(jSubNode, jsonSerializerOptions, jsonDocumentOptions, jsonNodeOptions);
}
}
else if (jNode is System.Text.Json.Nodes.JsonObject jNodeAsObject)
{
string[] keys = jNodeAsObject.Select(e => e.Key).ToArray();
foreach (string key in keys)
{
if (!PROPERTY_NAME_NARRAY.Equals(key))
continue;
int i = 0;
foreach (System.Text.Json.Nodes.JsonObject? jSubNode in jNodeAsObject[key]!.AsArray())
{
if (jSubNode == null)
continue;
foreach (var jSubKey in jSubNode)
{
string? json = jSubKey.Value?.ToJsonString(jsonSerializerOptions);
if (json != null)
jNodeAsObject[jSubKey.Key.Replace(PROPERTY_WILDCARD_NARRAY_ELEMENT, i.ToString())] = System.Text.Json.Nodes.JsonNode.Parse(json, jsonNodeOptions, jsonDocumentOptions);
}
i++;
}
}
jNodeAsObject.Remove(PROPERTY_NAME_NARRAY);
}
return jNode;
}
// NOTICE: 因为外层 JsonConverter 的缘故,这里不能使用 Newtonsoft.Json 序列化,会递归死循环
StringBuilder stringBuilder = new StringBuilder();
using TextWriter stringWriter = new StringWriter(stringBuilder);
Newtonsoft.Json.JsonSerializer serializer = Newtonsoft.Json.JsonSerializer.CreateDefault();
serializer.Serialize(stringWriter, obj, typeof(T));
string rawJson = stringBuilder.ToString();
System.Text.Json.JsonSerializerOptions jsonSerializerOptions = options ?? new System.Text.Json.JsonSerializerOptions();
System.Text.Json.JsonDocumentOptions jsonDocumentOptions = new System.Text.Json.JsonDocumentOptions()
{
AllowTrailingCommas = jsonSerializerOptions.AllowTrailingCommas,
CommentHandling = jsonSerializerOptions.ReadCommentHandling,
MaxDepth = jsonSerializerOptions.MaxDepth
};
System.Text.Json.Nodes.JsonNodeOptions jsonNodeOptions = new System.Text.Json.Nodes.JsonNodeOptions()
{
PropertyNameCaseInsensitive = jsonSerializerOptions.PropertyNameCaseInsensitive
};
System.Text.Json.Nodes.JsonNode jNode = System.Text.Json.Nodes.JsonNode.Parse(rawJson, jsonNodeOptions, jsonDocumentOptions)!;
return Flatten(jNode, jsonSerializerOptions, jsonDocumentOptions, jsonNodeOptions)
.ToJsonString(new System.Text.Json.JsonSerializerOptions()
{
WriteIndented = jsonSerializerOptions.WriteIndented,
Encoder = jsonSerializerOptions.Encoder
});
}
public static T DeserializeWhenHasNArray<T>(ref System.Text.Json.JsonElement jElement, System.Text.Json.JsonSerializerOptions? options = null)
where T : class, new()
{
var props = GetTypedSystemTextJsonProperties(typeof(T));
if (props.Count(p => p.IsNArrayProperty) != 1)
throw new System.Text.Json.JsonException("The number of `$n` properties must be only one.");
T result = new T();
foreach (System.Text.Json.JsonProperty jKey in jElement.EnumerateObject())
{
var prop = props.SingleOrDefault(e => e.PropertyName == jKey.Name);
if (prop != null)
{
// 处理普通属性
object? value = options is null ?
System.Text.Json.JsonSerializer.Deserialize(jKey.Value, prop.PropertyType, options) :
System.Text.Json.JsonSerializer.Deserialize(jKey.Value, prop.PropertyType, options);
prop.PropertyInfo.SetValue(result, value);
}
else if (TryMatchNArrayIndex(jKey.Name, out int index))
{
// 处理 $n 属性
var narrProp = props.Single(e => e.IsNArrayProperty);
object? value = narrProp.PropertyInfo.GetValue(result);
Array array = CreateOrExpandNArray(value, narrProp.PropertyType.GetElementType()!, index + 1);
object? element = CreateOrUpdateNArrayElement(array, narrProp.PropertyType.GetElementType()!, index, jKey.Name, jKey.Value, options);
narrProp.PropertyInfo.SetValue(result, array);
}
}
return result;
}
}
internal partial class JsonUtility
{
private sealed class InnerTypedJsonProperty
{
public string PropertyName { get; }
public PropertyInfo PropertyInfo { get; }
public Type PropertyType { get { return PropertyInfo.PropertyType; } }
public bool IsNArrayProperty { get; }
public InnerTypedJsonProperty(string propertyName, PropertyInfo propertyInfo, bool isNArrayProperty)
{
PropertyName = propertyName;
PropertyInfo = propertyInfo;
IsNArrayProperty = isNArrayProperty;
}
}
private static readonly Hashtable _mappedTypeJsonProperties = new Hashtable();
private static InnerTypedJsonProperty[] GetTypedNewtonsoftJsonProperties(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
string skey = "Newtosoft.Json:" + (type.AssemblyQualifiedName ?? type.GetHashCode().ToString());
var props = (InnerTypedJsonProperty[]?)_mappedTypeJsonProperties[skey];
if (props == null)
{
props = type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p =>
(p.CanRead && !p.GetCustomAttributes<Newtonsoft.Json.JsonIgnoreAttribute>(inherit: true).Any()) &&
(p.CanWrite || p.GetCustomAttributes<Newtonsoft.Json.JsonPropertyAttribute>(inherit: true).Any())
)
.Select(p =>
{
string name = p.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>(inherit: true)?.PropertyName ?? p.Name;
return new InnerTypedJsonProperty
(
propertyName: name,
propertyInfo: p,
isNArrayProperty: PROPERTY_NAME_NARRAY.Equals(name) && p.PropertyType.IsArray && p.PropertyType.GetElementType()!.IsClass
);
})
.ToArray();
_mappedTypeJsonProperties[skey] = props;
}
return props;
}
private static InnerTypedJsonProperty[] GetTypedSystemTextJsonProperties(Type type)
{
if (type == null) throw new ArgumentNullException(nameof(type));
string skey = "System.Text.Json:" + (type.AssemblyQualifiedName ?? type.GetHashCode().ToString());
var props = (InnerTypedJsonProperty[]?)_mappedTypeJsonProperties[skey];
if (props == null)
{
props = type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p =>
(p.CanRead && !p.GetCustomAttributes<System.Text.Json.Serialization.JsonIgnoreAttribute>(inherit: true).Any()) &&
(p.CanWrite || p.GetCustomAttributes<System.Text.Json.Serialization.JsonIncludeAttribute>(inherit: true).Any())
)
.Select(p =>
{
string name = p.GetCustomAttribute<System.Text.Json.Serialization.JsonPropertyNameAttribute>(inherit: true)?.Name ?? p.Name;
return new InnerTypedJsonProperty
(
propertyName: name,
propertyInfo: p,
isNArrayProperty: PROPERTY_NAME_NARRAY.Equals(name) && p.PropertyType.IsArray && p.PropertyType.GetElementType()!.IsClass
);
})
.ToArray();
_mappedTypeJsonProperties[skey] = props;
}
return props;
}
}
}

View File

@ -0,0 +1,21 @@
using System.Xml;
using Newtonsoft.Json;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Utilities
{
internal static class XmlUtility
{
public static string ConvertFromJson(string json)
{
XmlDocument xmlDocument = JsonConvert.DeserializeXmlNode(json);
return xmlDocument.InnerXml;
}
public static string ConvertToJson(string xml)
{
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xml);
return JsonConvert.SerializeXmlNode(xmlDocument);
}
}
}

View File

@ -0,0 +1,39 @@
using System;
using System.Security.Cryptography;
using System.Text;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.Utilities
{
/// <summary>
/// MD5 算法工具类。
/// </summary>
public static class MD5Utility
{
/// <summary>
/// 获取 MD5 信息摘要。
/// </summary>
/// <param name="bytes">信息字节数组。</param>
/// <returns>信息摘要。</returns>
public static string Hash(byte[] bytes)
{
if (bytes == null) throw new ArgumentNullException(nameof(bytes));
using MD5 md5 = MD5.Create();
byte[] hashBytes = md5.ComputeHash(bytes);
return BitConverter.ToString(hashBytes).Replace("-", "");
}
/// <summary>
/// 获取 MD5 信息摘要。
/// </summary>
/// <param name="message">文本信息。</param>
/// <returns>信息摘要。</returns>
public static string Hash(string message)
{
if (message == null) throw new ArgumentNullException(nameof(message));
byte[] bytes = Encoding.UTF8.GetBytes(message);
return Hash(bytes);
}
}
}

View File

@ -0,0 +1,153 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Flurl.Http;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2
{
/// <summary>
/// 一个微信支付 API HTTP 客户端。
/// </summary>
public partial class WechatTenpayClient : CommonClientBase, ICommonClient
{
/// <summary>
/// 获取当前客户端使用的微信商户平台凭证。
/// </summary>
public Settings.Credentials Credentials { get; }
/// <summary>
/// 用指定的配置项初始化 <see cref="WechatTenpayClient"/> 类的新实例。
/// </summary>
/// <param name="options">配置项。</param>
public WechatTenpayClient(WechatTenpayClientOptions options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
Credentials = new Settings.Credentials(options);
FlurlClient.BaseUrl = options.Endpoints ?? WechatTenpayEndpoints.DEFAULT;
FlurlClient.WithTimeout(TimeSpan.FromMilliseconds(options.Timeout));
FlurlClient.Configure((settings) =>
settings.HttpClientFactory = new Settings.HttpClientFactory(
options.MerchantCertificateBytes,
options.MerchantCertificatePassword ?? options.MerchantId
)
);
}
/// <summary>
/// 使用当前客户端生成一个新的 <see cref="IFlurlRequest"/> 对象。
/// </summary>
/// <param name="request"></param>
/// <param name="method"></param>
/// <param name="urlSegments"></param>
/// <returns></returns>
public IFlurlRequest CreateRequest(WechatTenpayRequest request, HttpMethod method, params object[] urlSegments)
{
IFlurlRequest flurlRequest = FlurlClient.Request(urlSegments).WithVerb(method);
if (request.Timeout != null)
{
flurlRequest.WithTimeout(TimeSpan.FromMilliseconds(request.Timeout.Value));
}
if (request.MerchantId == null)
{
request.MerchantId = Credentials.MerchantId;
}
if (request.AppId == null)
{
request.AppId = Credentials.AppId;
}
if (request is WechatTenpaySignableRequest signableRequest)
{
if (signableRequest.NonceString == null)
{
signableRequest.NonceString = Guid.NewGuid().ToString("N");
}
if (signableRequest.Signature == null)
{
string signType = signableRequest.SignType ?? Constants.SignTypes.MD5;
// TODO: 生成签名算法
throw new NotImplementedException();
}
}
return flurlRequest;
}
/// <summary>
/// 异步发起请求。
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="flurlRequest"></param>
/// <param name="data"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<T> SendRequestWithXmlAsync<T>(IFlurlRequest flurlRequest, object? data = null, CancellationToken cancellationToken = default)
where T : WechatTenpayResponse, 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;
if (isSimpleRequest)
{
using IFlurlResponse flurlResponse = await base.SendRequestAsync(flurlRequest, null, cancellationToken).ConfigureAwait(false);
return await WrapResponseWithXmlAsync<T>(flurlResponse, cancellationToken).ConfigureAwait(false);
}
else
{
string json = JsonSerializer.Serialize(data);
string xml = Utilities.XmlUtility.ConvertFromJson(json);
using HttpContent httpContent = new StringContent(xml, Encoding.UTF8, "text/xml");
using IFlurlResponse flurlResponse = await base.SendRequestAsync(flurlRequest, httpContent, cancellationToken).ConfigureAwait(false);
return await WrapResponseWithXmlAsync<T>(flurlResponse, cancellationToken).ConfigureAwait(false);
}
}
catch (FlurlHttpException ex)
{
throw new WechatTenpayException(ex.Message, ex);
}
}
private async Task<TResponse> WrapResponseWithXmlAsync<TResponse>(IFlurlResponse flurlResponse, CancellationToken cancellationToken = default)
where TResponse : WechatTenpayResponse, new()
{
TResponse tmp = await WrapResponseAsync<TResponse>(flurlResponse, cancellationToken);
byte tmpb1 = tmp.RawBytes.SkipWhile(b => b <= 32).FirstOrDefault(),
tmpb2 = tmp.RawBytes.Reverse().SkipWhile(b => b <= 32).FirstOrDefault();
bool xmlable = tmp.RawStatus == 200 && (tmpb1 == 60 && tmpb2 == 62); // "<...>"
TResponse result;
if (xmlable)
{
string xml = Encoding.UTF8.GetString(tmp.RawBytes);
string json = Utilities.XmlUtility.ConvertToJson(xml);
result = JsonSerializer.Deserialize<TResponse>(json);
result.RawStatus = tmp.RawStatus;
result.RawHeaders = tmp.RawHeaders;
result.RawBytes = tmp.RawBytes;
}
else
{
result = tmp;
}
return result;
}
}
}

View File

@ -0,0 +1,46 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2
{
/// <summary>
/// 一个用于构造 <see cref="WechatTenpayClient"/> 时使用的配置项。
/// </summary>
public class WechatTenpayClientOptions
{
/// <summary>
/// 获取或设置请求超时时间(单位:毫秒)。
/// <para>默认值30000</para>
/// </summary>
public int Timeout { get; set; } = 30 * 1000;
/// <summary>
/// 获取或设置微信支付 API 域名。
/// <para>默认值:<see cref="WechatTenpayEndpoints.DEFAULT"/></para>
/// </summary>
public string Endpoints { get; set; } = WechatTenpayEndpoints.DEFAULT;
/// <summary>
/// 获取或设置微信商户号。
/// </summary>
public string MerchantId { get; set; } = default!;
/// <summary>
/// 获取或设置微信商户 API 密钥(注意与 API v3 密钥相区分)。
/// </summary>
public string MerchantSecret { get; set; } = default!;
/// <summary>
/// 获取或设置微信商户 API 证书内容字节数组。仅部分敏感接口需要传入此参数。
/// </summary>
public byte[]? MerchantCertificateBytes { get; set; }
/// <summary>
/// 获取或设置微信商户 API 证书导入密码。仅部分敏感接口需要传入此参数。
/// <para>默认值:与 <see cref="MerchantId"/> 参数值相同。</para>
/// </summary>
public string? MerchantCertificatePassword { get; set; }
/// <summary>
/// 获取或设置微信 AppId。若一个商户号下关联多个 AppId 的,该参数可以置空,改为在请求时传入。
/// </summary>
public string? AppId { get; set; }
}
}

View File

@ -0,0 +1,23 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2
{
/// <summary>
/// 微信支付 API 接口域名。
/// </summary>
public static class WechatTenpayEndpoints
{
/// <summary>
/// 主域名(默认)。
/// </summary>
public const string DEFAULT = "https://api.mch.weixin.qq.com";
/// <summary>
/// 容灾备用域名。
/// </summary>
public const string BACKUP = "https://api2.mch.weixin.qq.com";
/// <summary>
/// 沙箱域名。
/// </summary>
public const string SANDBOX = "https://api.mch.weixin.qq.com/sandboxnew";
}
}

View File

@ -0,0 +1,27 @@
using System;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2
{
/// <summary>
/// 当调用微信支付 API 出错时引发的异常。
/// </summary>
public class WechatTenpayException : CommonExceptionBase
{
/// <inheritdoc/>
public WechatTenpayException()
{
}
/// <inheritdoc/>
public WechatTenpayException(string message)
: base(message)
{
}
/// <inheritdoc/>
public WechatTenpayException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@ -0,0 +1,56 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2
{
/// <summary>
/// 表示微信支付 API 请求的基类。
/// </summary>
public abstract class WechatTenpayRequest : ICommonRequest
{
/// <summary>
/// 获取或设置请求超时时间(单位:毫秒)。如果不指定将使用构造 <see cref="WechatTenpayClient"/> 时的 <see cref="WechatTenpayClientOptions.Timeout"/> 参数,这在需要指定特定耗时请求(比如上传或下载文件)的超时时间时很有用。
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[System.Text.Json.Serialization.JsonIgnore]
public virtual int? Timeout { get; set; }
/// <summary>
/// 获取或设置微信商户号。如果不指定将使用构造 <see cref="WechatTenpayClient"/> 时的 <see cref="WechatTenpayClientOptions.MerchantId"/> 参数。
/// </summary>
[Newtonsoft.Json.JsonProperty("mch_id")]
[System.Text.Json.Serialization.JsonPropertyName("mch_id")]
public string? MerchantId { get; set; }
/// <summary>
/// 获取或设置微信 AppId。如果不指定将使用构造 <see cref="WechatTenpayClient"/> 时的 <see cref="WechatTenpayClientOptions.AppId"/> 参数。
/// </summary>
[Newtonsoft.Json.JsonProperty("appid")]
[System.Text.Json.Serialization.JsonPropertyName("appid")]
public string? AppId { get; set; }
}
/// <summary>
/// 表示微信支付 API 请求的基类。
/// </summary>
public abstract class WechatTenpaySignableRequest : WechatTenpayRequest
{
/// <summary>
/// 获取或设置随机字符串。如果不指定将由系统自动生成。
/// </summary>
[Newtonsoft.Json.JsonProperty("nonce_str")]
[System.Text.Json.Serialization.JsonPropertyName("nonce_str")]
public virtual string? NonceString { get; set; }
/// <summary>
/// 获取或设置签名方式。需注意部分接口不支持指定签名方式。
/// </summary>
[Newtonsoft.Json.JsonProperty("sign_type")]
[System.Text.Json.Serialization.JsonPropertyName("sign_type")]
public virtual string? SignType { get; set; }
/// <summary>
/// 获取或设置签名。如果不指定将由系统自动生成。
/// </summary>
[Newtonsoft.Json.JsonProperty("sign")]
[System.Text.Json.Serialization.JsonPropertyName("sign")]
public virtual string? Signature { get; set; }
}
}

View File

@ -0,0 +1,149 @@
using System.Collections.Generic;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2
{
/// <summary>
/// 表示微信支付 API 响应的基类。
/// </summary>
public abstract class WechatTenpayResponse : 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.JsonProperty("return_code")]
[System.Text.Json.Serialization.JsonPropertyName("return_code")]
public virtual string? ReturnCode { get; set; }
/// <summary>
/// 获取微信支付 API 返回的状态描述。
/// </summary>
[Newtonsoft.Json.JsonProperty("return_msg")]
[System.Text.Json.Serialization.JsonPropertyName("return_msg")]
public virtual string? ReturnMessage { get; set; }
/// <summary>
/// 获取微信支付 API 返回的错误码。
/// </summary>
[Newtonsoft.Json.JsonProperty("err_code")]
[System.Text.Json.Serialization.JsonPropertyName("err_code")]
public virtual string? ErrorCode { get; set; }
/// <summary>
/// 获取微信支付 API 返回的状态描述。
/// </summary>
[Newtonsoft.Json.JsonProperty("err_code_des")]
[System.Text.Json.Serialization.JsonPropertyName("err_code_des")]
public virtual string? ErrorCodeDescription { get; set; }
/// <summary>
/// 获取或设置业务结果。
/// </summary>
[Newtonsoft.Json.JsonProperty("result_code")]
[System.Text.Json.Serialization.JsonPropertyName("result_code")]
public virtual string? ResultCode { get; set; }
/// <summary>
/// 获取或设置微信商户号。
/// </summary>
[Newtonsoft.Json.JsonProperty("mch_id")]
[System.Text.Json.Serialization.JsonPropertyName("mch_id")]
public virtual string? MerchantId { get; set; }
/// <summary>
/// 获取或设置微信 AppId。
/// </summary>
[Newtonsoft.Json.JsonProperty("appid")]
[System.Text.Json.Serialization.JsonPropertyName("appid")]
public virtual string? AppId { get; set; }
/// <summary>
/// 获取一个值,该值指示调用微信 API 是否成功(即 HTTP 状态码为 200、且 return_code 值 SUCCESS
/// </summary>
/// <returns></returns>
public virtual bool IsSuccessful()
{
bool ret = RawStatus == 200 && "SUCCESS".Equals(ReturnCode) && string.IsNullOrEmpty(ErrorCode);
if (ret)
{
return string.IsNullOrEmpty(ResultCode) || "SUCCESS".Equals(ResultCode);
}
return false;
}
}
/// <summary>
/// 表示微信支付 API 响应的基类。
/// </summary>
public abstract class WechatTenpaySignableResponse : WechatTenpayResponse
{
/// <summary>
/// 获取或设置随机字符串。
/// </summary>
[Newtonsoft.Json.JsonProperty("nonce_str")]
[System.Text.Json.Serialization.JsonPropertyName("nonce_str")]
public virtual string? NonceString { get; set; }
/// <summary>
/// 获取或设置签名类型。
/// </summary>
[Newtonsoft.Json.JsonProperty("sign_type")]
[System.Text.Json.Serialization.JsonPropertyName("sign_type")]
public virtual string? SignType { get; set; }
/// <summary>
/// 获取或设置签名。
/// </summary>
[Newtonsoft.Json.JsonProperty("sign")]
[System.Text.Json.Serialization.JsonPropertyName("sign")]
public virtual string? Signature { get; set; }
}
}

View File

@ -2,12 +2,23 @@
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System.Linq;
namespace Newtonsoft.Json.Converters
{
internal class TextualStringIListWithCommaConverter : JsonConverter<IList<string>?>
internal class TextualStringIListWithCommaConverter : JsonConverter
{
private readonly JsonConverter<List<string>?> _converter = new TextualStringListWithCommaConverter();
public override bool CanConvert(Type objectType)
{
bool ret = objectType == typeof(IList<string>) || objectType == typeof(List<string>);
if (!ret)
{
ret = objectType.IsGenericType &&
objectType.GetGenericTypeDefinition() == typeof(List<>) &&
objectType.GetElementType() == typeof(string);
}
return ret;
}
public override bool CanRead
{
@ -19,26 +30,32 @@ namespace Newtonsoft.Json.Converters
get { return true; }
}
public override IList<string>? ReadJson(JsonReader reader, Type objectType, IList<string>? existingValue, bool hasExistingValue, JsonSerializer serializer)
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
return _converter.ReadJson(reader, objectType, ConvertIListToList(existingValue), hasExistingValue, serializer);
}
public override void WriteJson(JsonWriter writer, IList<string>? value, JsonSerializer serializer)
{
_converter.WriteJson(writer, ConvertIListToList(value), serializer);
}
private List<string>? ConvertIListToList(IList<string>? src)
{
if (src == null)
if (reader.TokenType == JsonToken.Null)
{
return null;
}
else if (reader.TokenType == JsonToken.String)
{
string? value = serializer.Deserialize<string>(reader);
if (value == null)
return null;
if (string.IsNullOrEmpty(value))
return new List<string>();
List<string>? dest = src as List<string>;
if (dest != null)
return dest;
return value.Split(',').ToList();
}
return new List<string>(src);
throw new JsonReaderException();
}
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value != null)
writer.WriteValue(string.Join(",", value));
else
writer.WriteNull();
}
}
}

View File

@ -74,7 +74,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("transferable")]
[System.Text.Json.Serialization.JsonPropertyName("transferable")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.StringTypedNullableBooleanConverter))]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.TextualNullableBooleanConverter))]
public bool? IsTransferable { get; set; }
/// <summary>
@ -82,7 +82,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("shareable")]
[System.Text.Json.Serialization.JsonPropertyName("shareable")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.StringTypedNullableBooleanConverter))]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.TextualNullableBooleanConverter))]
public bool? IsShareable { get; set; }
/// <summary>

View File

@ -350,7 +350,7 @@ namespace SKIT.FlurlHttpClient.Wechat.TenpayV3.Models
/// </summary>
[Newtonsoft.Json.JsonProperty("need_collection")]
[System.Text.Json.Serialization.JsonPropertyName("need_collection")]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.StringTypedNullableBooleanConverter))]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Converters.TextualNullableBooleanConverter))]
public bool? RequireCollection { get; set; }
/// <summary>

View File

@ -0,0 +1,14 @@
{
"appid": "wx2421b1c4370ec43b",
"customs": "ZHENGZHOU_BS",
"mch_customs_no": "D00411",
"mch_id": "1262544101",
"order_fee": "13110",
"out_trade_no": "15112496832609",
"product_fee": "13110",
"sign": "8FF6CEF879FB9555CD580222E671E9D4",
"transaction_id": "1006930610201511241751403478",
"transport_fee": "0",
"fee_type": "CNY",
"sub_order_no": "15112496832609001"
}

View File

@ -0,0 +1,20 @@
{
"return_code": "",
"return_msg": "",
"sign_type": "",
"sign": "",
"appid": "",
"mch_id": "",
"result_code": "",
"err_code": "",
"err_code_des": "",
"state": "",
"transaction_id": "",
"out_trade_no": "",
"sub_order_no": "",
"sub_order_id": "",
"modify_time": "20091227091010",
"cert_check_result": "",
"verify_department": "",
"verify_department_trade_id": ""
}

View File

@ -0,0 +1,11 @@
{
"sign_type": "",
"sign": "",
"appid": "",
"mch_id": "",
"out_trade_no": "",
"transaction_id": "",
"sub_order_no": "",
"sub_order_id": "",
"customs": ""
}

View File

@ -0,0 +1,38 @@
{
"return_code": "SUCCESS",
"return_msg": "OK",
"sign": "C380BEC2BFD727A4B6845133519F3AD6",
"appid": "wxd678efh567hg6787",
"mch_id": "1230000109",
"result_code": "SUCCESS",
"err_code": "SUCCESS",
"err_code_des": "ERRCODE",
"transaction_id": "ERRMSG",
"count": 1,
"sub_order_no_0": "20150806125346",
"sub_order_id_0": "20150806125346",
"mch_customs_no_0": "mch_customs_no_0",
"customs_0": "SHANGHAI",
"duty_0": 888,
"fee_type_0": "CNY",
"order_fee_0": 888,
"transport_fee_0": 888,
"product_fee_0": 888,
"state_0": "UNDECLARED",
"explanation_0": "支付单已存在并且为非退单状态",
"modify_time_0": "20091227091010",
"cert_check_result_0": "UNCHECKED",
"sub_order_no_1": "201508061253461",
"sub_order_id_1": "201508061253461",
"mch_customs_no_1": "mch_customs_no_1",
"customs_1": "SHANGHAI1",
"duty_1": 8881,
"fee_type_1": "CNY1",
"order_fee_1": 8881,
"transport_fee_1": 8881,
"product_fee_1": 8881,
"state_1": "UNDECLARED1",
"explanation_1": "支付单已存在并且为非退单状态1",
"modify_time_1": "20091227091011",
"cert_check_result_1": "UNCHECKED1"
}

View File

@ -0,0 +1,8 @@
{
"appid": "wxab8acb865bb16371",
"customs": "SHENZHEN",
"mch_customs_no": "440316T004",
"mch_id": "1900006511",
"transaction_id": "4200000027201712197200279161",
"sign": "5D98596798203B0B1D61445707F71F87"
}

View File

@ -0,0 +1,18 @@
{
"return_code": "",
"return_msg": "",
"sign_type": "",
"sign": "",
"appid": "",
"mch_id": "",
"result_code": "",
"err_code": "",
"err_code_des": "",
"state": "",
"transaction_id": "",
"out_trade_no": "",
"sub_order_no": "",
"sub_order_id": "",
"modify_time": "20091227091010",
"explanation": ""
}

View File

@ -0,0 +1,17 @@
{
"appid": "wxdace645e0bc2c424",
"attach": "test",
"auth_code": "130050378319653252",
"body": "被扫测试",
"detail": "{\"cost_price\":1,\"receipt_id\":\"wx123\",\"goods_detail\":[{\"goods_id\":\"商品编码\",\"wxpay_goods_id\":\"1001\",\"goods_name\":\"iPhone6s 16G\",\"quantity\":1,\"price\":1},{\"goods_id\":\"商品编码\",\"wxpay_goods_id\":\"1002\",\"goods_name\":\"iPhone6s 32G\",\"quantity\":1,\"price\":1}]}",
"device_info": "TEST01",
"goods_tag": "MEETING",
"mch_id": "1900009001",
"nonce_str": "4b4f6f692547affd2c8fadb39fed603a",
"out_trade_no": "19000090011489146530",
"spbill_create_ip": "14.23.150.211",
"sub_mch_id": "11383918",
"total_fee": "503",
"version": "1.0",
"sign": "144FF79B7391FE1BD0708470B7D8A2E3"
}

View File

@ -0,0 +1,21 @@
{
"return_code": "SUCCESS",
"return_msg": "OK",
"appid": "wx2421b1c4370ec43b",
"mch_id": "10000100",
"device_info": "1000",
"nonce_str": "GOp3TRyMXzbMlkun",
"sign": "D6C76CB785F07992CDE05494BB7DF7FD",
"result_code": "SUCCESS",
"openid": "oUpF8uN95-Ptaags6E_roPHg7AG0",
"is_subscribe": "Y",
"trade_type": "MICROPAY",
"bank_type": "CCB_DEBIT",
"total_fee": "1",
"coupon_fee": "0",
"fee_type": "CNY",
"transaction_id": "1008450740201411110005820873",
"out_trade_no": "1415757673",
"attach": "订单额外描述",
"time_end": "20141111170043"
}

View File

@ -0,0 +1,10 @@
{
"appid": "wx8888888888888888",
"mch_id": "1900000109",
"device_info": "013467007045764",
"nonce_str": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
"sign": "C380BEC2BFD727A4B6845133519F3AD6",
"interface_url": "https://api.mch.weixin.qq.com/pay/batchreport/micropay/total",
"user_ip": "8.8.8.8",
"trades": "[{\n\t\t\"out_trade_no\": \"out_trade_no_test_1\",\n\t\t\"begin_time\": \"20160602203256\",\n\t\t\"end_time\": \"20160602203257\",\n\t\t\"state\": \"OK\",\n\t\t\"err_msg\": \"\"\n\t},\n\t{\n\t\t\"out_trade_no\": \"out_trade_no_test_2\",\n\t\t\"begin_time\": \"20160602203258\",\n\t\t\"end_time\": \"20160602203259\",\n\t\t\"state\": \"FAIL\",\n\t\t\"err_msg\": \"SYSTEMERROR\"\n\t}\n]"
}

View File

@ -0,0 +1,5 @@
{
"return_code": "SUCCESS",
"return_msg": "OK",
"result_code": "SUCCESS"
}

View File

@ -0,0 +1,9 @@
{
"appid": "",
"sub_appid": "",
"mch_id": "",
"sub_mch_id": "",
"auth_code": "",
"nonce_str": "",
"sign": ""
}

View File

@ -0,0 +1,14 @@
{
"return_code": "",
"return_msg": "",
"appid": "",
"sub_appid": "",
"mch_id": "",
"sub_mch_id": "",
"nonce_str": "",
"sign": "",
"result_code": "",
"err_code": "",
"openid": "",
"sub_openid": ""
}

View File

@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net472; netcoreapp3.1; 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" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<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.TenpayV2\SKIT.FlurlHttpClient.Wechat.TenpayV2.csproj" />
<ProjectReference Include="..\SKIT.FlurlHttpClient.Wechat.TestTools\SKIT.FlurlHttpClient.Wechat.TestTools.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,57 @@
using System.IO;
using System.Reflection;
using Xunit;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.UnitTests
{
public class TestCase_CodeReviewAnalyzer
{
private Assembly SourceAssembly { get; } = Assembly.Load("SKIT.FlurlHttpClient.Wechat.TenpayV2");
[Fact(DisplayName = "代码评审:分析 API 模型命名")]
public void TestApiModelsNaming()
{
CodeStyleUtil.VerifyApiModelsNaming(SourceAssembly, out var ex);
if (ex != null)
throw ex;
Assert.Null(ex);
}
[Fact(DisplayName = "代码评审:分析 API 模型定义")]
public void TestApiModelsDefinition()
{
string workdir = Path.Combine(TestConfigs.ProjectTestDirectory, "ModelSamples");
CodeStyleUtil.VerifyApiModelsDefinition(SourceAssembly, workdir, out var ex);
if (ex != null)
throw ex;
Assert.Null(ex);
}
[Fact(DisplayName = "代码评审:分析 API 接口命名")]
public void TestApiExtensionsNaming()
{
CodeStyleUtil.VerifyApiExtensionsNaming(SourceAssembly, out var ex);
if (ex != null)
throw ex;
Assert.Null(ex);
}
[Fact(DisplayName = "代码评审:分析代码规范")]
public void TestCodeStyle()
{
string workdir = Path.Combine(TestConfigs.ProjectSourceDirectory);
CodeStyleUtil.VerifySourceCodeStyle(workdir, out var ex);
if (ex != null)
throw ex;
Assert.Null(ex);
}
}
}

View File

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using Xunit;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.UnitTests
{
public class TestCase_JsonConverterTest
{
[Fact(DisplayName = "测试用例:自定义转换器之 `FlattenNArrayObjectConverterBase`")]
public void TestFlattenNArrayObjectConverter()
{
var newtonsoftJsonSerializer = new FlurlNewtonsoftJsonSerializer();
var systemTextJsonSerializer = new FlurlSystemTextJsonSerializer();
string rawJson = "{\"return_code\":\"RETURN_CODE\",\"return_msg\":\"RETURN_MSG\",\"sign\":\"SIGN\",\"appid\":\"APPID\",\"mch_id\":\"MCH_ID\",\"result_code\":\"RESULT_CODE\",\"err_code\":\"ERR_CODE\",\"err_code_des\":\"ERR_CODE_DESC\",\"transaction_id\":\"TRANSACTION_ID\",\"count\":2,\"sub_order_no_0\":\"SUB_ORDER_NO_0\",\"sub_order_id_0\":\"SUB_ORDER_ID_0\",\"mch_customs_no_0\":\"MCH_CUSTOMS_NO_0\",\"customs_0\":\"CUSTOMS_0\",\"duty_0\":10,\"fee_type_0\":\"FEE_TYPE_0\",\"order_fee_0\":10,\"transport_fee_0\":10,\"product_fee_0\":10,\"state_0\":\"STATE_0\",\"explanation_0\":\"EXPLANATION_0\",\"modify_time_0\":\"20000101112233\",\"cert_check_result_0\":\"UNCHECKED\",\"sub_order_no_1\":\"SUB_ORDER_NO_1\",\"sub_order_id_1\":\"SUB_ORDER_ID_1\",\"mch_customs_no_1\":\"MCH_CUSTOMS_NO_1\",\"customs_1\":\"CUSTOMS_1\",\"duty_1\":11,\"fee_type_1\":\"FEE_TYPE_1\",\"order_fee_1\":11,\"transport_fee_1\":11,\"product_fee_1\":11,\"state_1\":\"STATE_1\",\"explanation_1\":\"EXPLANATION_1\",\"modify_time_1\":\"20010101112233\",\"cert_check_result_1\":\"UNCHECKED1\"}";
var parsedObjByNewtonsoftJson = newtonsoftJsonSerializer.Deserialize<Models.QueryMerchantCustomsCustomDeclarationResponse>(rawJson);
var parsedObjBySystemTextJson = systemTextJsonSerializer.Deserialize<Models.QueryMerchantCustomsCustomDeclarationResponse>(rawJson);
Assert.Equal("RETURN_CODE", parsedObjByNewtonsoftJson.ReturnCode);
Assert.Equal("RETURN_CODE", parsedObjBySystemTextJson.ReturnCode);
Assert.Equal("RETURN_MSG", parsedObjByNewtonsoftJson.ReturnMessage);
Assert.Equal("RETURN_MSG", parsedObjBySystemTextJson.ReturnMessage);
Assert.Equal("SIGN", parsedObjByNewtonsoftJson.Signature);
Assert.Equal("SIGN", parsedObjBySystemTextJson.Signature);
Assert.Equal("APPID", parsedObjByNewtonsoftJson.AppId);
Assert.Equal("APPID", parsedObjBySystemTextJson.AppId);
Assert.Equal("MCH_ID", parsedObjByNewtonsoftJson.MerchantId);
Assert.Equal("MCH_ID", parsedObjBySystemTextJson.MerchantId);
Assert.Equal("RESULT_CODE", parsedObjByNewtonsoftJson.ResultCode);
Assert.Equal("RESULT_CODE", parsedObjBySystemTextJson.ResultCode);
Assert.Equal("ERR_CODE", parsedObjByNewtonsoftJson.ErrorCode);
Assert.Equal("ERR_CODE", parsedObjBySystemTextJson.ErrorCode);
Assert.Equal("ERR_CODE_DESC", parsedObjByNewtonsoftJson.ErrorCodeDescription);
Assert.Equal("ERR_CODE_DESC", parsedObjBySystemTextJson.ErrorCodeDescription);
Assert.Equal("TRANSACTION_ID", parsedObjByNewtonsoftJson.TransactionId);
Assert.Equal("TRANSACTION_ID", parsedObjBySystemTextJson.TransactionId);
Assert.Equal(2, parsedObjByNewtonsoftJson.RecordCount);
Assert.Equal(2, parsedObjBySystemTextJson.RecordCount);
Assert.Equal("SUB_ORDER_NO_0", parsedObjByNewtonsoftJson.RecordList[0].SubOrderNumber);
Assert.Equal("SUB_ORDER_NO_0", parsedObjBySystemTextJson.RecordList[0].SubOrderNumber);
Assert.Equal("SUB_ORDER_ID_0", parsedObjByNewtonsoftJson.RecordList[0].SubOrderId);
Assert.Equal("SUB_ORDER_ID_0", parsedObjBySystemTextJson.RecordList[0].SubOrderId);
Assert.Equal("MCH_CUSTOMS_NO_0", parsedObjByNewtonsoftJson.RecordList[0].MerchantCustomsNumber);
Assert.Equal("MCH_CUSTOMS_NO_0", parsedObjBySystemTextJson.RecordList[0].MerchantCustomsNumber);
Assert.Equal("CUSTOMS_0", parsedObjByNewtonsoftJson.RecordList[0].Customs);
Assert.Equal("CUSTOMS_0", parsedObjBySystemTextJson.RecordList[0].Customs);
Assert.Equal(10, parsedObjByNewtonsoftJson.RecordList[0].Duty);
Assert.Equal(10, parsedObjBySystemTextJson.RecordList[0].Duty);
Assert.Equal("FEE_TYPE_0", parsedObjByNewtonsoftJson.RecordList[0].FeeType);
Assert.Equal("FEE_TYPE_0", parsedObjBySystemTextJson.RecordList[0].FeeType);
Assert.Equal(DateTimeOffset.Parse("2000-01-01 11:22:33"), parsedObjByNewtonsoftJson.RecordList[0].ModifyTime);
Assert.Equal(DateTimeOffset.Parse("2000-01-01 11:22:33"), parsedObjBySystemTextJson.RecordList[0].ModifyTime);
Assert.Equal("SUB_ORDER_NO_1", parsedObjByNewtonsoftJson.RecordList[1].SubOrderNumber);
Assert.Equal("SUB_ORDER_NO_1", parsedObjBySystemTextJson.RecordList[1].SubOrderNumber);
Assert.Equal("SUB_ORDER_ID_1", parsedObjByNewtonsoftJson.RecordList[1].SubOrderId);
Assert.Equal("SUB_ORDER_ID_1", parsedObjBySystemTextJson.RecordList[1].SubOrderId);
Assert.Equal("MCH_CUSTOMS_NO_1", parsedObjByNewtonsoftJson.RecordList[1].MerchantCustomsNumber);
Assert.Equal("MCH_CUSTOMS_NO_1", parsedObjBySystemTextJson.RecordList[1].MerchantCustomsNumber);
Assert.Equal("CUSTOMS_1", parsedObjByNewtonsoftJson.RecordList[1].Customs);
Assert.Equal("CUSTOMS_1", parsedObjBySystemTextJson.RecordList[1].Customs);
Assert.Equal(11, parsedObjByNewtonsoftJson.RecordList[1].Duty);
Assert.Equal(11, parsedObjBySystemTextJson.RecordList[1].Duty);
Assert.Equal("FEE_TYPE_1", parsedObjByNewtonsoftJson.RecordList[1].FeeType);
Assert.Equal("FEE_TYPE_1", parsedObjBySystemTextJson.RecordList[1].FeeType);
Assert.Equal(DateTimeOffset.Parse("2001-01-01 11:22:33"), parsedObjByNewtonsoftJson.RecordList[1].ModifyTime);
Assert.Equal(DateTimeOffset.Parse("2001-01-01 11:22:33"), parsedObjBySystemTextJson.RecordList[1].ModifyTime);
string unparsedJsonByNewtonsoftJson = newtonsoftJsonSerializer.Serialize(parsedObjByNewtonsoftJson);
string unparsedJsonBySystemTextJson = systemTextJsonSerializer.Serialize(parsedObjByNewtonsoftJson);
Assert.Contains("return_code", unparsedJsonByNewtonsoftJson);
Assert.Contains("return_code", unparsedJsonBySystemTextJson);
Assert.Contains("return_msg", unparsedJsonByNewtonsoftJson);
Assert.Contains("return_msg", unparsedJsonBySystemTextJson);
Assert.Contains("sub_order_no_0", unparsedJsonByNewtonsoftJson);
Assert.Contains("sub_order_no_0", unparsedJsonBySystemTextJson);
Assert.Contains("sub_order_id_0", unparsedJsonByNewtonsoftJson);
Assert.Contains("sub_order_id_0", unparsedJsonBySystemTextJson);
Assert.DoesNotContain("#n", unparsedJsonByNewtonsoftJson);
Assert.DoesNotContain("#n", unparsedJsonBySystemTextJson);
Assert.DoesNotContain("$n", unparsedJsonByNewtonsoftJson);
Assert.DoesNotContain("$n", unparsedJsonBySystemTextJson);
}
}
}

View File

@ -0,0 +1,16 @@
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.UnitTests
{
class TestClients
{
static TestClients()
{
Instance = new WechatTenpayClient(new WechatTenpayClientOptions()
{
MerchantId = TestConfigs.WechatMerchantId,
MerchantSecret = TestConfigs.WechatMerchantSecret
});
}
public static readonly WechatTenpayClient Instance;
}
}

View File

@ -0,0 +1,43 @@
using System;
using System.IO;
using System.Text.Json;
namespace SKIT.FlurlHttpClient.Wechat.TenpayV2.UnitTests
{
class TestConfigs
{
static TestConfigs()
{
// NOTICE: 请在项目根目录下按照 appsettings.json 的格式新建 appsettings.local.json 填入测试参数。
// WARNING: 请在 DEBUG 模式下运行测试用例。
// WARNING: 敏感信息请不要提交到 git
try
{
using var stream = File.OpenRead("appsettings.local.json");
using var jdoc = JsonDocument.Parse(stream);
var config = jdoc.RootElement.GetProperty("TestConfig");
WechatAppId = config.GetProperty("AppId").GetString()!;
WechatMerchantId = config.GetProperty("MerchantId").GetString()!;
WechatMerchantSecret = config.GetProperty("MerchantSecret").GetString()!;
WechatOpenId = config.GetProperty("OpenId").GetString()!;
ProjectSourceDirectory = jdoc.RootElement.GetProperty("ProjectSourceDirectory").GetString()!;
ProjectTestDirectory = jdoc.RootElement.GetProperty("ProjectTestDirectory").GetString()!;
}
catch (Exception ex)
{
throw new Exception("加载配置文件 appsettings.local.json 失败,请查看 `InnerException` 了解具体失败原因", ex);
}
}
public static readonly string WechatAppId;
public static readonly string WechatMerchantId;
public static readonly string WechatMerchantSecret;
public static readonly string WechatOpenId;
public static readonly string ProjectSourceDirectory;
public static readonly string ProjectTestDirectory;
}
}

View File

@ -0,0 +1,10 @@
{
"TestConfig": {
"AppId": "请在此填写用于测试的微信 AppId",
"MerchantId": "请在此填写用于测试的微信商户号",
"MerchantSecret": "请在此填写用于测试的微信商户 API 密钥",
"OpenId": "请在此填写用于测试的微信用户唯一标识"
},
"ProjectSourceDirectory": "请输入当前 SDK 项目所在的目录完整路径,如 C:\\Project\\src\\SKIT.FlurlHttpClient.Wechat.TenpayV2\\",
"ProjectTestDirectory": "请输入当前测试项目所在的目录完整路径,如 C:\\Project\\test\\SKIT.FlurlHttpClient.Wechat.TenpayV2.UnitTests\\"
}

View File

@ -0,0 +1,10 @@
{
"TestConfig": {
"AppId": "wxd861802f8e303335",
"MerchantId": "1601103314",
"MerchantSecret": "f09b03a7a1902b5b4913856f1fd07ab1",
"OpenId": "owNIE0msADfoPjhpy2cz1qL4vImw"
},
"ProjectSourceDirectory": "D:\\Projects\\_SKIT\\stack-dotnet\\DotNetCore.SKIT.FlurlHttpClient.Wechat\\src\\SKIT.FlurlHttpClient.Wechat.TenpayV2",
"ProjectTestDirectory": "D:\\Projects\\_SKIT\\stack-dotnet\\DotNetCore.SKIT.FlurlHttpClient.Wechat\\test\\SKIT.FlurlHttpClient.Wechat.TenpayV2.UnitTests"
}

View File

@ -411,7 +411,7 @@ namespace SKIT.FlurlHttpClient.Wechat
string extCodeFileName = Path.GetFileName(extCodeFilePath);
string[] segments = File.ReadAllText(extCodeFilePath)
.Split("<summary>", StringSplitOptions.RemoveEmptyEntries)
.Split(new string[] { "<summary>" }, StringSplitOptions.RemoveEmptyEntries)
.Where(e => e.Contains("Async") && !e.Contains("public static class"))
.ToArray();
for (int i = 0; i < segments.Length; i++)
@ -440,9 +440,9 @@ namespace SKIT.FlurlHttpClient.Wechat
string expectedMethod = regexApi.Groups[1].Value.Trim();
string expectedUrl = regexApi.Groups[2].Value.Split('?')[0].Trim();
string actualMethod = sourceCode.Contains(".CreateRequest(request, new HttpMethod(\"") ?
sourceCode.Split(".CreateRequest(request, new HttpMethod(\"")[1].Split("\"")[0] :
sourceCode.Split(new string[] { ".CreateRequest(request, new HttpMethod(\"" }, StringSplitOptions.None)[1].Split('\"')[0] :
sourceCode.Contains(".CreateRequest(request, HttpMethod.") ?
sourceCode.Split(".CreateRequest(request, HttpMethod.")[1].Split(",")[0].Split(")")[0] :
sourceCode.Split(new string[] { ".CreateRequest(request, HttpMethod." }, StringSplitOptions.None)[1].Split(',')[0].Split(')')[0] :
string.Empty;
if (!string.Equals(expectedMethod, actualMethod, StringComparison.OrdinalIgnoreCase))
{
@ -452,17 +452,17 @@ namespace SKIT.FlurlHttpClient.Wechat
// 比对请求路由
string actualUrl = sourceCode
.Split("CreateRequest(request,", StringSplitOptions.RemoveEmptyEntries)[1]
.Substring(sourceCode.Split("CreateRequest(request,", StringSplitOptions.RemoveEmptyEntries)[1].Split(",")[0].Length + 1)
.Split(new string[] { "CreateRequest(request," }, StringSplitOptions.RemoveEmptyEntries)[1]
.Substring(sourceCode.Split(new string[] { "CreateRequest(request," }, StringSplitOptions.RemoveEmptyEntries)[1].Split(',')[0].Length + 1)
.Split('\n')[0]
.Trim()
.TrimEnd(')', ';')
.Trim();
string[] expectedUrlSegments = expectedUrl.Split('/', StringSplitOptions.RemoveEmptyEntries);
string[] actualUrlSegments = actualUrl.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(e => e.Trim()).ToArray();
string[] expectedUrlSegments = expectedUrl.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries);
string[] actualUrlSegments = actualUrl.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries).Select(e => e.Trim()).ToArray();
if (expectedUrlSegments.Length != actualUrlSegments.Length)
{
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口路由不一致(段数不等)。"));
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口路由不一致:节长不等(实际 {actualUrlSegments.Length},期望 {expectedUrlSegments.Length})。"));
return false;
}
else
@ -475,7 +475,7 @@ namespace SKIT.FlurlHttpClient.Wechat
{
if (actualUrlSegment.StartsWith("\""))
{
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口路由不一致(预期为变量展位符,实际为常量字符串)。"));
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口路由不一致:第 {urlSegmentIndex} 节值不同。"));
break;
}
}
@ -484,7 +484,7 @@ namespace SKIT.FlurlHttpClient.Wechat
actualUrlSegment = actualUrlSegment.Replace("\"", string.Empty).Trim('/');
if (!string.Equals(expectedUrlSegment, actualUrlSegment))
{
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口路由不一致(预期为常量展位符,实际为变量字符串)。"));
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口路由不一致:第 {urlSegmentIndex} 节值不同。"));
break;
}
}

View File

@ -1,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1; net6.0</TargetFrameworks>
<LangVersion>9.0</LangVersion>
<TargetFrameworks>net472; netcoreapp3.1; net6.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>