Treat itfx body code/msg as business result, store full request/response, and write Data.serviceNo/trackNo back to the order. Co-authored-by: Cursor <cursoragent@cursor.com>
146 lines
4.2 KiB
C#
146 lines
4.2 KiB
C#
using System.Text.Json;
|
||
using Oms.Domain.OrdersAggregate.Entities;
|
||
|
||
namespace Oms.Application.Integrations.Itfx;
|
||
|
||
/// <summary>
|
||
/// 解析 itfx createOrderOne 成功响应中的 Data 字段,并回写订单。
|
||
/// </summary>
|
||
internal static class ItfxCreateOrderOneDataParser
|
||
{
|
||
public sealed class CreateOrderOneData
|
||
{
|
||
public string? ServiceNo { get; init; }
|
||
|
||
public string? TrackNo { get; init; }
|
||
|
||
public string? ClientNo { get; init; }
|
||
|
||
public string? LabelUrl { get; init; }
|
||
}
|
||
|
||
public static bool TryParse(string? responseBody, out CreateOrderOneData? data)
|
||
{
|
||
data = null;
|
||
if (string.IsNullOrWhiteSpace(responseBody))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
try
|
||
{
|
||
using var doc = JsonDocument.Parse(responseBody);
|
||
var root = doc.RootElement;
|
||
if (root.ValueKind != JsonValueKind.Object)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (!TryGetPropertyIgnoreCase(root, out var dataElement, "data", "Data")
|
||
|| dataElement.ValueKind != JsonValueKind.Object)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
data = new CreateOrderOneData
|
||
{
|
||
ServiceNo = ReadString(dataElement, "serviceNo", "ServiceNo"),
|
||
TrackNo = ReadString(dataElement, "trackNo", "TrackNo"),
|
||
ClientNo = ReadString(dataElement, "clientNo", "ClientNo"),
|
||
LabelUrl = ReadString(dataElement, "labelUrl", "LabelUrl"),
|
||
};
|
||
return !string.IsNullOrWhiteSpace(data.ServiceNo)
|
||
|| !string.IsNullOrWhiteSpace(data.TrackNo)
|
||
|| !string.IsNullOrWhiteSpace(data.ClientNo)
|
||
|| !string.IsNullOrWhiteSpace(data.LabelUrl);
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将下游返回写入订单:服务商单号 ← Data.serviceNo,跟踪号 ← Data.trackNo。
|
||
/// 空值不覆盖已有字段。
|
||
/// </summary>
|
||
public static bool ApplyToOrder(Order order, CreateOrderOneData data)
|
||
{
|
||
var changed = false;
|
||
|
||
if (!string.IsNullOrWhiteSpace(data.ServiceNo))
|
||
{
|
||
var value = data.ServiceNo.Trim();
|
||
if (!string.Equals(order.DownstreamServiceNo, value, StringComparison.Ordinal))
|
||
{
|
||
order.DownstreamServiceNo = value;
|
||
changed = true;
|
||
}
|
||
}
|
||
|
||
if (!string.IsNullOrWhiteSpace(data.TrackNo))
|
||
{
|
||
var value = data.TrackNo.Trim();
|
||
if (!string.Equals(order.TrackNo, value, StringComparison.Ordinal))
|
||
{
|
||
order.TrackNo = value;
|
||
changed = true;
|
||
}
|
||
}
|
||
|
||
if (!string.IsNullOrWhiteSpace(data.LabelUrl))
|
||
{
|
||
var value = data.LabelUrl.Trim();
|
||
if (!string.Equals(order.LabelUrl, value, StringComparison.Ordinal))
|
||
{
|
||
order.LabelUrl = value;
|
||
changed = true;
|
||
}
|
||
}
|
||
|
||
return changed;
|
||
}
|
||
|
||
private static bool TryGetPropertyIgnoreCase(JsonElement root, out JsonElement value, params string[] names)
|
||
{
|
||
foreach (var name in names)
|
||
{
|
||
if (root.TryGetProperty(name, out value))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
|
||
foreach (var prop in root.EnumerateObject())
|
||
{
|
||
foreach (var name in names)
|
||
{
|
||
if (prop.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
value = prop.Value;
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
value = default;
|
||
return false;
|
||
}
|
||
|
||
private static string? ReadString(JsonElement root, params string[] names)
|
||
{
|
||
if (!TryGetPropertyIgnoreCase(root, out var prop, names))
|
||
{
|
||
return null;
|
||
}
|
||
|
||
return prop.ValueKind switch
|
||
{
|
||
JsonValueKind.String => prop.GetString()?.Trim(),
|
||
JsonValueKind.Number => prop.GetRawText(),
|
||
JsonValueKind.Null => null,
|
||
_ => null
|
||
};
|
||
}
|
||
}
|