mirror of
https://gitee.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat.git
synced 2025-07-15 05:13:17 +08:00
feat(tenpayv2): 导入项目
This commit is contained in:
parent
47575857b6
commit
7205686f71
@ -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}
|
||||
|
@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("SKIT.FlurlHttpClient.Wechat.Ads.UnitTests")]
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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!;
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
{
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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!;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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>();
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("SKIT.FlurlHttpClient.Wechat.TenpayV2.UnitTests")]
|
31
src/SKIT.FlurlHttpClient.Wechat.TenpayV2/README.md
Normal file
31
src/SKIT.FlurlHttpClient.Wechat.TenpayV2/README.md
Normal file
@ -0,0 +1,31 @@
|
||||
## SKIT.FlurlHttpClient.Wechat.TenpayV2
|
||||
|
||||
[](https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat)
|
||||
[](https://github.com/fudiwei/DotNetCore.SKIT.FlurlHttpClient.Wechat)
|
||||
[](https://www.nuget.org/packages/SKIT.FlurlHttpClient.Wechat.TenpayV2)
|
||||
[](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)。
|
@ -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>
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
153
src/SKIT.FlurlHttpClient.Wechat.TenpayV2/WechatTenpayClient.cs
Normal file
153
src/SKIT.FlurlHttpClient.Wechat.TenpayV2/WechatTenpayClient.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
||||
}
|
149
src/SKIT.FlurlHttpClient.Wechat.TenpayV2/WechatTenpayResponse.cs
Normal file
149
src/SKIT.FlurlHttpClient.Wechat.TenpayV2/WechatTenpayResponse.cs
Normal 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; }
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
}
|
@ -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": ""
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
{
|
||||
"sign_type": "",
|
||||
"sign": "",
|
||||
"appid": "",
|
||||
"mch_id": "",
|
||||
"out_trade_no": "",
|
||||
"transaction_id": "",
|
||||
"sub_order_no": "",
|
||||
"sub_order_id": "",
|
||||
"customs": ""
|
||||
}
|
@ -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"
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"appid": "wxab8acb865bb16371",
|
||||
"customs": "SHENZHEN",
|
||||
"mch_customs_no": "440316T004",
|
||||
"mch_id": "1900006511",
|
||||
"transaction_id": "4200000027201712197200279161",
|
||||
"sign": "5D98596798203B0B1D61445707F71F87"
|
||||
}
|
@ -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": ""
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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]"
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"return_code": "SUCCESS",
|
||||
"return_msg": "OK",
|
||||
"result_code": "SUCCESS"
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
{
|
||||
"appid": "",
|
||||
"sub_appid": "",
|
||||
"mch_id": "",
|
||||
"sub_mch_id": "",
|
||||
"auth_code": "",
|
||||
"nonce_str": "",
|
||||
"sign": ""
|
||||
}
|
@ -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": ""
|
||||
}
|
@ -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>
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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\\"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user