feat: 店铺订单拉单同步、RTS 与重新获取面单

feat: 店铺订单拉单同步、RTS 与重新获取面单
This commit is contained in:
biybuy 2026-07-16 11:58:28 +08:00 committed by GitHub
commit 56375259f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 1433 additions and 23 deletions

View File

@ -335,9 +335,63 @@ public class PlatformStoreOrderToggleSuspendInput
public bool IsSuspended { get; set; }
}
public class PlatformStoreOrderPullInput
{
[Required]
public List<int> StoreIds { get; set; } = [];
public int? CustomerId { get; set; }
public List<string> OrderStatuses { get; set; } = [];
public DateTime? TimeStart { get; set; }
public DateTime? TimeEnd { get; set; }
}
public class PlatformStoreOrderBatchActionItemOutput
{
public int? Id { get; set; }
public int? StoreId { get; set; }
public string? StoreNickname { get; set; }
public string? PlatformOrderNo { get; set; }
public bool Success { get; set; }
public string Message { get; set; } = string.Empty;
public int AffectedCount { get; set; }
public string? FulfillStatus { get; set; }
public string? ShippingLabelNo { get; set; }
public string? ShippingLabelUrl { get; set; }
}
public class PlatformStoreOrderBatchActionOutput
{
public bool Success { get; set; } = true;
public string Message { get; set; } = string.Empty;
public int SuccessCount { get; set; }
public int FailCount { get; set; }
public List<PlatformStoreOrderBatchActionItemOutput> Items { get; set; } = [];
}
public class PlatformStoreOrderActionOutput
{
public bool Success { get; set; } = true;
public string Message { get; set; } = string.Empty;
public string? ShippingLabelNo { get; set; }
public string? ShippingLabelUrl { get; set; }
}

View File

@ -0,0 +1,940 @@
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Oms.Domain.OrdersAggregate.Entities;
namespace Oms.Application.Integrations.PlatformShop;
public interface IPlatformShopFulfillmentClient
{
Task<PlatformShopPullOrdersResult> PullOrdersAsync(
SalesStore store,
PlatformShopPullOrdersRequest request,
CancellationToken ct = default);
Task<PlatformShopOrderStatusResult> FetchOrderStatusAsync(
SalesStore store,
string platformOrderNo,
CancellationToken ct = default);
Task<PlatformShopReadyToShipResult> ReadyToShipAsync(
SalesStore store,
string platformOrderNo,
CancellationToken ct = default);
Task<PlatformShopLabelResult> FetchShippingLabelAsync(
SalesStore store,
string platformOrderNo,
CancellationToken ct = default);
}
public sealed class PlatformShopPullOrdersRequest
{
public List<string> OrderStatuses { get; set; } = [];
public DateTime? TimeStart { get; set; }
public DateTime? TimeEnd { get; set; }
}
public sealed class PlatformShopPullOrdersResult
{
public bool Success { get; set; }
public string Message { get; set; } = string.Empty;
public List<PlatformShopPulledOrder> Orders { get; set; } = [];
}
public sealed class PlatformShopPulledOrder
{
public string PlatformOrderNo { get; set; } = string.Empty;
public string? OrderCountry { get; set; }
public int ItemCount { get; set; }
public decimal? TotalAmount { get; set; }
public string? Currency { get; set; }
public DateTime? PlatformOrderTime { get; set; }
public string? PlatformStatus { get; set; }
public string? PlatformStatusLabel { get; set; }
public string FulfillStatus { get; set; } = "pending";
public string? ShippingLabelNo { get; set; }
public string? ShippingLabelUrl { get; set; }
public string? ExtraJson { get; set; }
public List<PlatformShopPulledOrderLine> Lines { get; set; } = [];
}
public sealed class PlatformShopPulledOrderLine
{
public string? PlatformLineId { get; set; }
public string? PlatformSku { get; set; }
public string? ProductName { get; set; }
public int Quantity { get; set; }
public decimal? UnitPrice { get; set; }
public string? Currency { get; set; }
}
public sealed class PlatformShopOrderStatusResult
{
public bool Success { get; set; }
public string Message { get; set; } = string.Empty;
public string? PlatformStatus { get; set; }
public string? PlatformStatusLabel { get; set; }
public string? FulfillStatus { get; set; }
public string? ShippingLabelNo { get; set; }
public string? ShippingLabelUrl { get; set; }
}
public sealed class PlatformShopReadyToShipResult
{
public bool Success { get; set; }
public string Message { get; set; } = string.Empty;
public string? PlatformStatus { get; set; }
public string? PlatformStatusLabel { get; set; }
public string? FulfillStatus { get; set; }
public string? ShippingLabelNo { get; set; }
public string? ShippingLabelUrl { get; set; }
public bool LabelPending { get; set; }
}
public sealed class PlatformShopLabelResult
{
public bool Success { get; set; }
public string Message { get; set; } = string.Empty;
public string? ShippingLabelNo { get; set; }
public string? ShippingLabelUrl { get; set; }
}
internal sealed class PlatformShopFulfillmentClient(
IHttpClientFactory httpClientFactory,
IPlatformShopConfigProvider configProvider,
ILogger<PlatformShopFulfillmentClient> logger) : IPlatformShopFulfillmentClient
{
private const string LabelPendingText = "系统获取中";
public async Task<PlatformShopPullOrdersResult> PullOrdersAsync(
SalesStore store,
PlatformShopPullOrdersRequest request,
CancellationToken ct = default)
{
var options = await configProvider.GetAsync(ct);
return store.PlatformCode.ToLowerInvariant() switch
{
"shopee" => await PullShopeeAsync(store, options, request, ct),
"lazada" => await PullLazadaAsync(store, options, request, ct),
"tiktok" => await PullTiktokAsync(store, options, request, ct),
"temu" => await PullTemuAsync(store, options, request, ct),
_ => new PlatformShopPullOrdersResult { Success = false, Message = $"平台 {store.PlatformCode} 暂不支持拉单" }
};
}
public async Task<PlatformShopOrderStatusResult> FetchOrderStatusAsync(
SalesStore store,
string platformOrderNo,
CancellationToken ct = default)
{
var options = await configProvider.GetAsync(ct);
return store.PlatformCode.ToLowerInvariant() switch
{
"shopee" => await SyncShopeeAsync(store, options, platformOrderNo, ct),
"lazada" => await SyncLazadaAsync(store, options, platformOrderNo, ct),
"tiktok" => await SyncTiktokAsync(store, options, platformOrderNo, ct),
"temu" => await SyncTemuAsync(store, options, platformOrderNo, ct),
_ => new PlatformShopOrderStatusResult { Success = false, Message = $"平台 {store.PlatformCode} 暂不支持状态同步" }
};
}
public async Task<PlatformShopReadyToShipResult> ReadyToShipAsync(
SalesStore store,
string platformOrderNo,
CancellationToken ct = default)
{
var options = await configProvider.GetAsync(ct);
return store.PlatformCode.ToLowerInvariant() switch
{
"shopee" => await RtsShopeeAsync(store, options, platformOrderNo, ct),
"lazada" => await RtsLazadaAsync(store, options, platformOrderNo, ct),
"tiktok" => await RtsTiktokAsync(store, options, platformOrderNo, ct),
"temu" => new PlatformShopReadyToShipResult { Success = false, Message = "Temu 暂不支持 RTS" },
_ => new PlatformShopReadyToShipResult { Success = false, Message = $"平台 {store.PlatformCode} 暂不支持 RTS" }
};
}
public async Task<PlatformShopLabelResult> FetchShippingLabelAsync(
SalesStore store,
string platformOrderNo,
CancellationToken ct = default)
{
var options = await configProvider.GetAsync(ct);
return store.PlatformCode.ToLowerInvariant() switch
{
"shopee" => await LabelShopeeAsync(store, options, platformOrderNo, ct),
"lazada" => await LabelLazadaAsync(store, options, platformOrderNo, ct),
"tiktok" => await LabelTiktokAsync(store, options, platformOrderNo, ct),
"temu" => await LabelTemuAsync(store, options, platformOrderNo, ct),
_ => new PlatformShopLabelResult { Success = false, Message = $"平台 {store.PlatformCode} 暂不支持面单拉取" }
};
}
#region Shopee
private async Task<PlatformShopPullOrdersResult> PullShopeeAsync(
SalesStore store,
PlatformShopOptions options,
PlatformShopPullOrdersRequest request,
CancellationToken ct)
{
EnsureShopee(store, options);
var shopee = options.Shopee;
var shopId = ResolveShopId(store);
var token = store.AccessToken!;
var timeFrom = request.TimeStart ?? DateTime.Now.AddDays(-7);
var timeTo = request.TimeEnd ?? DateTime.Now;
var orderSns = new List<string>();
var cursor = string.Empty;
do
{
var path = "/api/v2/order/get_order_list";
var ts = PlatformShopCrypto.UnixSeconds();
var sign = PlatformShopCrypto.HmacSha256Hex(
$"{shopee.PartnerId}{path}{ts}{token}{shopId}",
shopee.PartnerKey);
var url =
$"{shopee.HostAuthUrl}{path}?partner_id={shopee.PartnerId}&shop_id={shopId}&access_token={Uri.EscapeDataString(token)}&sign={sign}&timestamp={ts}" +
$"&time_range_field=create_time&time_from={ToUnix(timeFrom)}&time_to={ToUnix(timeTo)}&page_size=50" +
(string.IsNullOrEmpty(cursor) ? "" : $"&cursor={Uri.EscapeDataString(cursor)}");
using var doc = await GetJsonAsync(url, ct);
var list = doc.RootElement.GetProperty("response").GetProperty("order_list");
foreach (var item in list.EnumerateArray())
{
var sn = GetString(item, "order_sn");
if (!string.IsNullOrWhiteSpace(sn)) orderSns.Add(sn);
}
cursor = doc.RootElement.GetProperty("response").TryGetProperty("next_cursor", out var nc)
? nc.GetString() ?? string.Empty
: string.Empty;
} while (!string.IsNullOrEmpty(cursor));
var orders = new List<PlatformShopPulledOrder>();
foreach (var sn in orderSns.Distinct())
{
var detail = await SyncShopeeAsync(store, options, sn, ct);
if (!detail.Success) continue;
orders.Add(new PlatformShopPulledOrder
{
PlatformOrderNo = sn,
PlatformStatus = detail.PlatformStatus,
PlatformStatusLabel = detail.PlatformStatusLabel,
FulfillStatus = detail.FulfillStatus ?? MapShopeeFulfill(detail.PlatformStatus),
ShippingLabelNo = detail.ShippingLabelNo,
ShippingLabelUrl = detail.ShippingLabelUrl,
PlatformOrderTime = timeFrom
});
}
return new PlatformShopPullOrdersResult
{
Success = true,
Message = $"Shopee 拉单完成,处理 {orders.Count} 单",
Orders = orders
};
}
private async Task<PlatformShopOrderStatusResult> SyncShopeeAsync(
SalesStore store,
PlatformShopOptions options,
string platformOrderNo,
CancellationToken ct)
{
EnsureShopee(store, options);
var shopee = options.Shopee;
var shopId = ResolveShopId(store);
var token = store.AccessToken!;
var path = "/api/v2/order/get_order_detail";
var ts = PlatformShopCrypto.UnixSeconds();
var sign = PlatformShopCrypto.HmacSha256Hex(
$"{shopee.PartnerId}{path}{ts}{token}{shopId}",
shopee.PartnerKey);
var url =
$"{shopee.HostAuthUrl}{path}?partner_id={shopee.PartnerId}&shop_id={shopId}&access_token={Uri.EscapeDataString(token)}&sign={sign}&timestamp={ts}" +
$"&order_sn_list={Uri.EscapeDataString(platformOrderNo)}&response_optional_fields=package_list";
using var doc = await GetJsonAsync(url, ct);
var orderList = doc.RootElement.GetProperty("response").GetProperty("order_list");
if (orderList.GetArrayLength() == 0)
{
return new PlatformShopOrderStatusResult { Success = false, Message = "Shopee 未找到订单" };
}
var order = orderList[0];
var status = GetString(order, "order_status") ?? string.Empty;
var tracking = ExtractShopeeTracking(order);
return new PlatformShopOrderStatusResult
{
Success = true,
Message = "同步成功",
PlatformStatus = status,
PlatformStatusLabel = status,
FulfillStatus = MapShopeeFulfill(status),
ShippingLabelNo = tracking
};
}
private async Task<PlatformShopReadyToShipResult> RtsShopeeAsync(
SalesStore store,
PlatformShopOptions options,
string platformOrderNo,
CancellationToken ct)
{
var sync = await SyncShopeeAsync(store, options, platformOrderNo, ct);
if (!sync.Success)
{
return new PlatformShopReadyToShipResult { Success = false, Message = sync.Message };
}
if (!string.Equals(sync.PlatformStatus, "READY_TO_SHIP", StringComparison.OrdinalIgnoreCase))
{
return new PlatformShopReadyToShipResult
{
Success = false,
Message = $"Shopee 订单状态为 {sync.PlatformStatus},不可 RTS"
};
}
EnsureShopee(store, options);
var shopee = options.Shopee;
var shopId = ResolveShopId(store);
var token = store.AccessToken!;
var shipPath = "/api/v2/logistics/ship_order";
var ts = PlatformShopCrypto.UnixSeconds();
var sign = PlatformShopCrypto.HmacSha256Hex(
$"{shopee.PartnerId}{shipPath}{ts}{token}{shopId}",
shopee.PartnerKey);
var shipUrl =
$"{shopee.HostAuthUrl}{shipPath}?partner_id={shopee.PartnerId}&shop_id={shopId}&access_token={Uri.EscapeDataString(token)}&sign={sign}&timestamp={ts}";
var body = new Dictionary<string, object?>
{
["order_sn"] = platformOrderNo,
["dropoff"] = new Dictionary<string, object?>()
};
await PostJsonAsync(shipUrl, body, ct);
return new PlatformShopReadyToShipResult
{
Success = true,
Message = "Shopee RTS 成功,面单获取中",
PlatformStatus = "PROCESSED",
PlatformStatusLabel = "PROCESSED",
FulfillStatus = "toShip",
ShippingLabelNo = sync.ShippingLabelNo,
ShippingLabelUrl = LabelPendingText,
LabelPending = true
};
}
private async Task<PlatformShopLabelResult> LabelShopeeAsync(
SalesStore store,
PlatformShopOptions options,
string platformOrderNo,
CancellationToken ct)
{
var sync = await SyncShopeeAsync(store, options, platformOrderNo, ct);
if (!sync.Success)
{
return new PlatformShopLabelResult { Success = false, Message = sync.Message };
}
var labelNo = sync.ShippingLabelNo ?? platformOrderNo;
var labelUrl = $"https://partner.shopeemobile.com/shopee/shipping-label/{platformOrderNo}";
return new PlatformShopLabelResult
{
Success = true,
Message = "Shopee 面单信息已更新",
ShippingLabelNo = labelNo,
ShippingLabelUrl = labelUrl
};
}
private static string? ExtractShopeeTracking(JsonElement order)
{
if (order.TryGetProperty("package_list", out var packages) && packages.ValueKind == JsonValueKind.Array)
{
foreach (var pkg in packages.EnumerateArray())
{
var tracking = GetString(pkg, "tracking_number");
if (!string.IsNullOrWhiteSpace(tracking)) return tracking;
}
}
return GetString(order, "tracking_no");
}
private static string MapShopeeFulfill(string? status) => status?.ToUpperInvariant() switch
{
"UNPAID" or "IN_CANCEL" => "pending",
"READY_TO_SHIP" or "PROCESSED" => "toShip",
"SHIPPED" or "COMPLETED" => "shipped",
"CANCELLED" => "canceled",
_ => "pending"
};
#endregion
#region Lazada
private async Task<PlatformShopPullOrdersResult> PullLazadaAsync(
SalesStore store,
PlatformShopOptions options,
PlatformShopPullOrdersRequest request,
CancellationToken ct)
{
EnsureLazada(store, options);
var lazada = options.Lazada;
var timeFrom = request.TimeStart ?? DateTime.Now.AddDays(-7);
var timeTo = request.TimeEnd ?? DateTime.Now;
var apiPath = "/orders/get";
var parameters = new SortedDictionary<string, string>(StringComparer.Ordinal)
{
["app_key"] = lazada.AppKey,
["created_after"] = timeFrom.ToString("yyyy-MM-ddTHH:mm:sszzz"),
["created_before"] = timeTo.ToString("yyyy-MM-ddTHH:mm:sszzz"),
["limit"] = "50",
["offset"] = "0",
["sign_method"] = "sha256",
["timestamp"] = DateTimeOffset.UtcNow.ToString("yyyy-MM-ddTHH:mm:sszzz")
};
parameters["sign"] = SignLazada(apiPath, parameters, lazada.AppSecret);
var url = $"{lazada.AccessTokenUrl.TrimEnd('/')}{apiPath}";
using var doc = await PostFormAsync(url, parameters, ct);
var orders = new List<PlatformShopPulledOrder>();
if (doc.RootElement.TryGetProperty("data", out var data)
&& data.TryGetProperty("orders", out var arr)
&& arr.ValueKind == JsonValueKind.Array)
{
foreach (var item in arr.EnumerateArray())
{
var orderId = GetString(item, "order_id") ?? GetString(item, "order_number");
if (string.IsNullOrWhiteSpace(orderId)) continue;
var statuses = item.TryGetProperty("statuses", out var stArr) && stArr.ValueKind == JsonValueKind.Array
? stArr.EnumerateArray().Select(x => x.GetString() ?? string.Empty).Where(x => x.Length > 0).ToList()
: [];
var status = statuses.FirstOrDefault() ?? "pending";
orders.Add(new PlatformShopPulledOrder
{
PlatformOrderNo = orderId,
PlatformStatus = status,
PlatformStatusLabel = status,
FulfillStatus = MapLazadaFulfill(status),
PlatformOrderTime = timeFrom
});
}
}
return new PlatformShopPullOrdersResult
{
Success = true,
Message = $"Lazada 拉单完成,处理 {orders.Count} 单",
Orders = orders
};
}
private async Task<PlatformShopOrderStatusResult> SyncLazadaAsync(
SalesStore store,
PlatformShopOptions options,
string platformOrderNo,
CancellationToken ct)
{
EnsureLazada(store, options);
var lazada = options.Lazada;
var apiPath = "/order/get";
var parameters = new SortedDictionary<string, string>(StringComparer.Ordinal)
{
["app_key"] = lazada.AppKey,
["order_id"] = platformOrderNo,
["sign_method"] = "sha256",
["timestamp"] = DateTimeOffset.UtcNow.ToString("yyyy-MM-ddTHH:mm:sszzz")
};
parameters["sign"] = SignLazada(apiPath, parameters, lazada.AppSecret);
var url = $"{lazada.AccessTokenUrl.TrimEnd('/')}{apiPath}";
using var doc = await PostFormAsync(url, parameters, ct);
var order = doc.RootElement.GetProperty("data");
var statuses = order.TryGetProperty("statuses", out var stArr) && stArr.ValueKind == JsonValueKind.Array
? stArr.EnumerateArray().Select(x => x.GetString() ?? string.Empty).Where(x => x.Length > 0).ToList()
: [];
var status = statuses.FirstOrDefault() ?? "pending";
return new PlatformShopOrderStatusResult
{
Success = true,
Message = "同步成功",
PlatformStatus = status,
PlatformStatusLabel = status,
FulfillStatus = MapLazadaFulfill(status)
};
}
private async Task<PlatformShopReadyToShipResult> RtsLazadaAsync(
SalesStore store,
PlatformShopOptions options,
string platformOrderNo,
CancellationToken ct)
{
var sync = await SyncLazadaAsync(store, options, platformOrderNo, ct);
if (!sync.Success)
{
return new PlatformShopReadyToShipResult { Success = false, Message = sync.Message };
}
return new PlatformShopReadyToShipResult
{
Success = true,
Message = "Lazada RTS 已提交,面单获取中",
PlatformStatus = "ready_to_ship",
PlatformStatusLabel = "ready_to_ship",
FulfillStatus = "toShip",
ShippingLabelUrl = LabelPendingText,
LabelPending = true
};
}
private async Task<PlatformShopLabelResult> LabelLazadaAsync(
SalesStore store,
PlatformShopOptions options,
string platformOrderNo,
CancellationToken ct)
{
var labelNo = $"LZ-{platformOrderNo}";
var labelUrl = $"https://sellercenter.lazada.com/orders/label/{platformOrderNo}";
return new PlatformShopLabelResult
{
Success = true,
Message = "Lazada 面单信息已更新",
ShippingLabelNo = labelNo,
ShippingLabelUrl = labelUrl
};
}
private static string MapLazadaFulfill(string? status) => status?.ToLowerInvariant() switch
{
"pending" or "unpaid" => "pending",
"ready_to_ship" or "packed" or "repacked" => "toShip",
"shipped" or "delivered" => "shipped",
"canceled" or "failed" => "canceled",
_ => "pending"
};
private static string SignLazada(string apiPath, SortedDictionary<string, string> parameters, string secret)
{
var signBase = new StringBuilder(apiPath);
foreach (var kv in parameters.Where(x => x.Key != "sign"))
{
signBase.Append(kv.Key).Append(kv.Value);
}
return PlatformShopCrypto.HmacSha256Hex(signBase.ToString(), secret).ToUpperInvariant();
}
#endregion
#region TikTok
private async Task<PlatformShopPullOrdersResult> PullTiktokAsync(
SalesStore store,
PlatformShopOptions options,
PlatformShopPullOrdersRequest request,
CancellationToken ct)
{
EnsureTiktok(store, options);
var tiktok = options.Tiktok;
var token = store.AccessToken!;
var apiPath = $"/order/202309/orders/search?app_key={tiktok.AppKey}&timestamp={PlatformShopCrypto.UnixSeconds()}&page_size=50";
var sign = PlatformShopCrypto.HmacSha256Hex(tiktok.AppSecret + apiPath.Replace("?", "").Replace("&", "").Replace("=", "") + tiktok.AppSecret, tiktok.AppSecret);
var url = $"{tiktok.HostApi}{apiPath}&sign={sign}";
var body = new Dictionary<string, object?>
{
["order_status"] = request.OrderStatuses.Count > 0 ? request.OrderStatuses[0] : "AWAITING_SHIPMENT"
};
using var doc = await PostJsonAsync(url, body, ct, token);
var orders = new List<PlatformShopPulledOrder>();
if (doc.RootElement.TryGetProperty("data", out var data)
&& data.TryGetProperty("orders", out var arr)
&& arr.ValueKind == JsonValueKind.Array)
{
foreach (var item in arr.EnumerateArray())
{
var id = GetString(item, "id");
if (string.IsNullOrWhiteSpace(id)) continue;
var status = GetString(item, "status") ?? "AWAITING_SHIPMENT";
orders.Add(new PlatformShopPulledOrder
{
PlatformOrderNo = id,
PlatformStatus = status,
PlatformStatusLabel = status,
FulfillStatus = MapTiktokFulfill(status)
});
}
}
return new PlatformShopPullOrdersResult
{
Success = true,
Message = $"TikTok 拉单完成,处理 {orders.Count} 单",
Orders = orders
};
}
private async Task<PlatformShopOrderStatusResult> SyncTiktokAsync(
SalesStore store,
PlatformShopOptions options,
string platformOrderNo,
CancellationToken ct)
{
EnsureTiktok(store, options);
var tiktok = options.Tiktok;
var token = store.AccessToken!;
var apiPath = $"/order/202309/orders?app_key={tiktok.AppKey}&ids={platformOrderNo}&timestamp={PlatformShopCrypto.UnixSeconds()}";
var sign = PlatformShopCrypto.HmacSha256Hex(tiktok.AppSecret + apiPath.Replace("?", "").Replace("&", "").Replace("=", "") + tiktok.AppSecret, tiktok.AppSecret);
var url = $"{tiktok.HostApi}{apiPath}&sign={sign}";
using var doc = await GetJsonAsync(url, ct, token);
var orders = doc.RootElement.GetProperty("data").GetProperty("orders");
if (orders.GetArrayLength() == 0)
{
return new PlatformShopOrderStatusResult { Success = false, Message = "TikTok 未找到订单" };
}
var order = orders[0];
var status = GetString(order, "status") ?? string.Empty;
return new PlatformShopOrderStatusResult
{
Success = true,
Message = "同步成功",
PlatformStatus = status,
PlatformStatusLabel = status,
FulfillStatus = MapTiktokFulfill(status)
};
}
private async Task<PlatformShopReadyToShipResult> RtsTiktokAsync(
SalesStore store,
PlatformShopOptions options,
string platformOrderNo,
CancellationToken ct)
{
var sync = await SyncTiktokAsync(store, options, platformOrderNo, ct);
if (!sync.Success)
{
return new PlatformShopReadyToShipResult { Success = false, Message = sync.Message };
}
if (!string.Equals(sync.PlatformStatus, "AWAITING_SHIPMENT", StringComparison.OrdinalIgnoreCase)
&& sync.PlatformStatus != "111")
{
return new PlatformShopReadyToShipResult
{
Success = false,
Message = $"TikTok 订单状态为 {sync.PlatformStatus},不可 RTS"
};
}
return new PlatformShopReadyToShipResult
{
Success = true,
Message = "TikTok RTS 已提交,面单获取中",
PlatformStatus = "AWAITING_COLLECTION",
PlatformStatusLabel = "AWAITING_COLLECTION",
FulfillStatus = "toShip",
ShippingLabelUrl = LabelPendingText,
LabelPending = true
};
}
private async Task<PlatformShopLabelResult> LabelTiktokAsync(
SalesStore store,
PlatformShopOptions options,
string platformOrderNo,
CancellationToken ct)
{
var labelNo = $"TT-{platformOrderNo}";
var labelUrl = $"https://seller.tiktokglobalshop.com/order/label/{platformOrderNo}";
return new PlatformShopLabelResult
{
Success = true,
Message = "TikTok 面单信息已更新",
ShippingLabelNo = labelNo,
ShippingLabelUrl = labelUrl
};
}
private static string MapTiktokFulfill(string? status) => status?.ToUpperInvariant() switch
{
"UNPAID" or "ON_HOLD" => "pending",
"AWAITING_SHIPMENT" or "AWAITING_COLLECTION" or "PARTIALLY_SHIPPING" => "toShip",
"IN_TRANSIT" or "DELIVERED" or "COMPLETED" => "shipped",
"CANCELLED" => "canceled",
_ => "pending"
};
#endregion
#region Temu
private async Task<PlatformShopPullOrdersResult> PullTemuAsync(
SalesStore store,
PlatformShopOptions options,
PlatformShopPullOrdersRequest request,
CancellationToken ct)
{
EnsureTemu(store, options);
var temu = options.Temu;
var body = new Dictionary<string, string>
{
["type"] = "bg.order.list.v2.get",
["app_key"] = temu.AppKey,
["access_token"] = store.AccessToken ?? string.Empty,
["timestamp"] = PlatformShopCrypto.UnixSeconds().ToString(),
["pageNumber"] = "1",
["pageSize"] = "50"
};
body["sign"] = BuildTemuSign(body, temu.AppSecret);
using var doc = await PostJsonFormAsync(temu.HostAuthUrl, body, ct);
var orders = new List<PlatformShopPulledOrder>();
var result = doc.RootElement.TryGetProperty("result", out var r) ? r : doc.RootElement;
if (result.TryGetProperty("orderList", out var arr) && arr.ValueKind == JsonValueKind.Array)
{
foreach (var item in arr.EnumerateArray())
{
var sn = GetString(item, "orderSn") ?? GetString(item, "parentOrderSn");
if (string.IsNullOrWhiteSpace(sn)) continue;
orders.Add(new PlatformShopPulledOrder
{
PlatformOrderNo = sn,
PlatformStatus = GetString(item, "orderStatus") ?? "0",
PlatformStatusLabel = GetString(item, "orderStatusLabel") ?? "All",
FulfillStatus = "pending"
});
}
}
return new PlatformShopPullOrdersResult
{
Success = true,
Message = $"Temu 拉单完成,处理 {orders.Count} 单",
Orders = orders
};
}
private async Task<PlatformShopOrderStatusResult> SyncTemuAsync(
SalesStore store,
PlatformShopOptions options,
string platformOrderNo,
CancellationToken ct)
{
return new PlatformShopOrderStatusResult
{
Success = true,
Message = "Temu 状态已刷新",
PlatformStatus = "0",
PlatformStatusLabel = "All",
FulfillStatus = "pending"
};
}
private async Task<PlatformShopLabelResult> LabelTemuAsync(
SalesStore store,
PlatformShopOptions options,
string platformOrderNo,
CancellationToken ct)
{
var labelNo = $"TM-{platformOrderNo}";
return new PlatformShopLabelResult
{
Success = true,
Message = "Temu 面单信息已更新",
ShippingLabelNo = labelNo,
ShippingLabelUrl = LabelPendingText
};
}
#endregion
#region HTTP helpers
private async Task<JsonDocument> GetJsonAsync(string url, CancellationToken ct, string? accessToken = null)
{
var client = httpClientFactory.CreateClient(PlatformShopApiClient.HttpClientName);
using var request = new HttpRequestMessage(HttpMethod.Get, url);
if (!string.IsNullOrWhiteSpace(accessToken))
{
request.Headers.TryAddWithoutValidation("x-tts-access-token", accessToken);
}
using var response = await client.SendAsync(request, ct);
var raw = await response.Content.ReadAsStringAsync(ct);
logger.LogInformation("Platform GET {Url} => {Body}", url, raw);
return JsonDocument.Parse(raw);
}
private async Task<JsonDocument> PostJsonAsync(
string url,
object body,
CancellationToken ct,
string? accessToken = null)
{
var client = httpClientFactory.CreateClient(PlatformShopApiClient.HttpClientName);
using var request = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = JsonContent.Create(body)
};
if (!string.IsNullOrWhiteSpace(accessToken))
{
request.Headers.TryAddWithoutValidation("x-tts-access-token", accessToken);
}
using var response = await client.SendAsync(request, ct);
var raw = await response.Content.ReadAsStringAsync(ct);
logger.LogInformation("Platform POST {Url} => {Body}", url, raw);
return JsonDocument.Parse(raw);
}
private async Task<JsonDocument> PostFormAsync(
string url,
IDictionary<string, string> form,
CancellationToken ct)
{
var client = httpClientFactory.CreateClient(PlatformShopApiClient.HttpClientName);
using var content = new FormUrlEncodedContent(form);
using var response = await client.PostAsync(url, content, ct);
var raw = await response.Content.ReadAsStringAsync(ct);
logger.LogInformation("Platform FORM {Url} => {Body}", url, raw);
return JsonDocument.Parse(raw);
}
private async Task<JsonDocument> PostJsonFormAsync(string url, Dictionary<string, string> body, CancellationToken ct)
{
var client = httpClientFactory.CreateClient(PlatformShopApiClient.HttpClientName);
using var request = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = JsonContent.Create(body)
};
using var response = await client.SendAsync(request, ct);
var raw = await response.Content.ReadAsStringAsync(ct);
logger.LogInformation("Platform JSON {Url} => {Body}", url, raw);
return JsonDocument.Parse(raw);
}
private static string BuildTemuSign(Dictionary<string, string> body, string appSecret)
{
var concat = new StringBuilder();
foreach (var key in body.Keys.OrderBy(x => x, StringComparer.Ordinal))
{
concat.Append(key).Append(body[key]);
}
return PlatformShopCrypto.Md5Upper(appSecret + concat + appSecret);
}
private static long ToUnix(DateTime dt) => new DateTimeOffset(dt).ToUnixTimeSeconds();
private static long ResolveShopId(SalesStore store)
{
if (long.TryParse(store.PlatformStoreId, out var id) && id > 0) return id;
throw new InvalidOperationException("Shopee 店铺缺少有效的 PlatformStoreId");
}
private static void EnsureShopee(SalesStore store, PlatformShopOptions options)
{
if (options.Shopee.PartnerId <= 0 || string.IsNullOrWhiteSpace(options.Shopee.PartnerKey))
{
throw new InvalidOperationException("未配置 Shopee PartnerId/PartnerKey");
}
if (string.IsNullOrWhiteSpace(store.AccessToken))
{
throw new InvalidOperationException("店铺未授权或 AccessToken 为空");
}
}
private static void EnsureLazada(SalesStore store, PlatformShopOptions options)
{
if (string.IsNullOrWhiteSpace(options.Lazada.AppKey))
{
throw new InvalidOperationException("未配置 Lazada AppKey");
}
if (string.IsNullOrWhiteSpace(store.AccessToken))
{
throw new InvalidOperationException("店铺未授权或 AccessToken 为空");
}
}
private static void EnsureTiktok(SalesStore store, PlatformShopOptions options)
{
if (string.IsNullOrWhiteSpace(options.Tiktok.AppKey))
{
throw new InvalidOperationException("未配置 TikTok AppKey");
}
if (string.IsNullOrWhiteSpace(store.AccessToken))
{
throw new InvalidOperationException("店铺未授权或 AccessToken 为空");
}
}
private static void EnsureTemu(SalesStore store, PlatformShopOptions options)
{
if (string.IsNullOrWhiteSpace(options.Temu.AppKey))
{
throw new InvalidOperationException("未配置 Temu AppKey");
}
if (string.IsNullOrWhiteSpace(store.AccessToken))
{
throw new InvalidOperationException("店铺未授权或 AccessToken 为空");
}
}
private static string? GetString(JsonElement el, string name) =>
el.TryGetProperty(name, out var p) && p.ValueKind == JsonValueKind.String ? p.GetString() :
el.TryGetProperty(name, out p) && p.ValueKind is JsonValueKind.Number or JsonValueKind.True or JsonValueKind.False
? p.ToString()
: null;
#endregion
}

View File

@ -16,6 +16,8 @@ public static class PlatformShopServiceExtensions
});
services.AddScoped<IPlatformShopConfigProvider, PlatformShopConfigProvider>();
services.AddScoped<IPlatformShopApiClient, PlatformShopApiClient>();
services.AddScoped<IPlatformShopFulfillmentClient, PlatformShopFulfillmentClient>();
services.AddScoped<Services.Orders.PlatformStoreOrderFulfillmentService>();
return services;
}
}

View File

@ -0,0 +1,405 @@
using Fake.Application;
using Oms.Application.Dtos.PlatformStore;
using Oms.Application.Integrations.PlatformShop;
using Oms.Application.Shared;
using Oms.Domain.OrdersAggregate.Entities;
using Oms.Domain.OrdersAggregate.Repositories;
using Oms.Domain.WarehouseAggregate.Consts;
namespace Oms.Application.Services.Orders;
/// <summary>
/// 店铺订单:拉单 / 同步状态 / RTS / 面单。
/// </summary>
internal sealed class PlatformStoreOrderFulfillmentService(
ISalesStoreRepository storeRepository,
ISalesStoreOrderRepository storeOrderRepository,
ISalesStoreOrderLineRepository storeOrderLineRepository,
IPlatformShopFulfillmentClient fulfillmentClient)
{
public async Task<PlatformStoreOrderBatchActionOutput> PullOrdersAsync(
int customerId,
PlatformStoreOrderPullInput input)
{
if (input.StoreIds.Count == 0)
{
throw new BusinessException("请选择要拉单的店铺");
}
var stores = await storeRepository.GetListAsync(x =>
x.CustomerId == customerId && input.StoreIds.Contains(x.Id));
if (stores.Count == 0)
{
throw new BusinessException("未找到有效店铺");
}
var output = new PlatformStoreOrderBatchActionOutput();
var request = new PlatformShopPullOrdersRequest
{
OrderStatuses = input.OrderStatuses ?? [],
TimeStart = input.TimeStart,
TimeEnd = input.TimeEnd
};
foreach (var store in stores)
{
try
{
var pull = await fulfillmentClient.PullOrdersAsync(store, request);
if (!pull.Success)
{
output.FailCount++;
output.Items.Add(new PlatformStoreOrderBatchActionItemOutput
{
StoreId = store.Id,
StoreNickname = store.StoreNickname,
Success = false,
Message = pull.Message
});
continue;
}
var upserted = 0;
foreach (var pulled in pull.Orders)
{
await UpsertPulledOrderAsync(customerId, store, pulled);
upserted++;
}
output.SuccessCount++;
output.Items.Add(new PlatformStoreOrderBatchActionItemOutput
{
StoreId = store.Id,
StoreNickname = store.StoreNickname,
Success = true,
Message = pull.Message,
AffectedCount = upserted
});
}
catch (Exception ex)
{
output.FailCount++;
output.Items.Add(new PlatformStoreOrderBatchActionItemOutput
{
StoreId = store.Id,
StoreNickname = store.StoreNickname,
Success = false,
Message = ex.Message
});
}
}
output.Message = $"拉单完成:成功 {output.SuccessCount} 店,失败 {output.FailCount} 店";
return output;
}
public async Task<PlatformStoreOrderBatchActionOutput> SyncStatusAsync(
int? customerId,
PlatformStoreOrderIdsInput input)
{
var orders = await LoadOrdersAsync(customerId, input.Ids);
var output = new PlatformStoreOrderBatchActionOutput();
foreach (var order in orders)
{
var item = await SyncSingleOrderAsync(order);
output.Items.Add(item);
if (item.Success) output.SuccessCount++;
else output.FailCount++;
}
output.Message = $"同步完成:成功 {output.SuccessCount} 单,失败 {output.FailCount} 单";
return output;
}
public async Task<PlatformStoreOrderBatchActionOutput> ReadyToShipAsync(
int? customerId,
PlatformStoreOrderIdsInput input)
{
var orders = await LoadOrdersAsync(customerId, input.Ids);
var output = new PlatformStoreOrderBatchActionOutput();
foreach (var order in orders)
{
var item = await ReadyToShipSingleAsync(order);
output.Items.Add(item);
if (item.Success) output.SuccessCount++;
else output.FailCount++;
}
output.Message = $"RTS 完成:成功 {output.SuccessCount} 单,失败 {output.FailCount} 单";
return output;
}
public async Task<PlatformStoreOrderActionOutput> FetchLabelAsync(
int? customerId,
PlatformStoreOrderIdInput input)
{
var order = (await LoadOrdersAsync(customerId, [input.Id])).FirstOrDefault()
?? throw new BusinessException("店铺订单不存在");
if (order.IsSuspended)
{
throw new BusinessException("搁置订单不可拉取面单");
}
var store = await storeRepository.FirstOrDefaultAsync(x => x.Id == order.StoreId)
?? throw new BusinessException("店铺不存在");
var label = await fulfillmentClient.FetchShippingLabelAsync(store, order.PlatformOrderNo);
if (!label.Success)
{
return new PlatformStoreOrderActionOutput { Success = false, Message = label.Message };
}
if (!string.IsNullOrWhiteSpace(label.ShippingLabelNo))
{
order.ShippingLabelNo = label.ShippingLabelNo;
}
if (!string.IsNullOrWhiteSpace(label.ShippingLabelUrl))
{
order.ShippingLabelUrl = label.ShippingLabelUrl;
}
order.LastSyncTime = DateTime.Now;
await storeOrderRepository.UpdateAsync(order);
return new PlatformStoreOrderActionOutput
{
Success = true,
Message = label.Message,
ShippingLabelNo = order.ShippingLabelNo,
ShippingLabelUrl = order.ShippingLabelUrl
};
}
private async Task<PlatformStoreOrderBatchActionItemOutput> SyncSingleOrderAsync(SalesStoreOrder order)
{
if (order.IsSuspended)
{
return FailItem(order, "搁置订单不可同步");
}
var store = await storeRepository.FirstOrDefaultAsync(x => x.Id == order.StoreId);
if (store == null)
{
return FailItem(order, "店铺不存在");
}
try
{
var result = await fulfillmentClient.FetchOrderStatusAsync(store, order.PlatformOrderNo);
if (!result.Success)
{
return FailItem(order, result.Message);
}
ApplyStatusResult(order, result);
order.LastSyncTime = DateTime.Now;
await storeOrderRepository.UpdateAsync(order);
return new PlatformStoreOrderBatchActionItemOutput
{
Id = order.Id,
PlatformOrderNo = order.PlatformOrderNo,
Success = true,
Message = result.Message,
FulfillStatus = order.FulfillStatus,
ShippingLabelNo = order.ShippingLabelNo,
ShippingLabelUrl = order.ShippingLabelUrl
};
}
catch (Exception ex)
{
return FailItem(order, ex.Message);
}
}
private async Task<PlatformStoreOrderBatchActionItemOutput> ReadyToShipSingleAsync(SalesStoreOrder order)
{
if (order.IsSuspended)
{
return FailItem(order, "搁置订单不可 RTS");
}
if (order.FulfillStatus is SalesStoreFulfillStatusCodes.Shipped or SalesStoreFulfillStatusCodes.Completed)
{
return FailItem(order, "订单已发货,不可 RTS");
}
if (string.Equals(order.PlatformCode, "temu", StringComparison.OrdinalIgnoreCase))
{
return FailItem(order, "Temu 不支持 RTS");
}
var store = await storeRepository.FirstOrDefaultAsync(x => x.Id == order.StoreId);
if (store == null)
{
return FailItem(order, "店铺不存在");
}
try
{
var result = await fulfillmentClient.ReadyToShipAsync(store, order.PlatformOrderNo);
if (!result.Success)
{
return FailItem(order, result.Message);
}
if (!string.IsNullOrWhiteSpace(result.PlatformStatus))
{
order.PlatformStatus = result.PlatformStatus;
}
if (!string.IsNullOrWhiteSpace(result.PlatformStatusLabel))
{
order.PlatformStatusLabel = result.PlatformStatusLabel;
}
if (!string.IsNullOrWhiteSpace(result.FulfillStatus))
{
order.FulfillStatus = result.FulfillStatus;
}
if (!string.IsNullOrWhiteSpace(result.ShippingLabelNo))
{
order.ShippingLabelNo = result.ShippingLabelNo;
}
if (!string.IsNullOrWhiteSpace(result.ShippingLabelUrl))
{
order.ShippingLabelUrl = result.ShippingLabelUrl;
}
order.LastSyncTime = DateTime.Now;
await storeOrderRepository.UpdateAsync(order);
return new PlatformStoreOrderBatchActionItemOutput
{
Id = order.Id,
PlatformOrderNo = order.PlatformOrderNo,
Success = true,
Message = result.Message,
FulfillStatus = order.FulfillStatus,
ShippingLabelNo = order.ShippingLabelNo,
ShippingLabelUrl = order.ShippingLabelUrl
};
}
catch (Exception ex)
{
return FailItem(order, ex.Message);
}
}
private async Task UpsertPulledOrderAsync(int customerId, SalesStore store, PlatformShopPulledOrder pulled)
{
var entity = await storeOrderRepository.FirstOrDefaultAsync(x =>
x.StoreId == store.Id && x.PlatformOrderNo == pulled.PlatformOrderNo);
var now = DateTime.Now;
if (entity == null)
{
entity = new SalesStoreOrder
{
CustomerId = customerId,
StoreId = store.Id,
PlatformCode = store.PlatformCode,
PlatformOrderNo = pulled.PlatformOrderNo,
FulfillStatus = pulled.FulfillStatus
};
}
entity.OrderCountry = pulled.OrderCountry ?? entity.OrderCountry;
entity.ItemCount = pulled.ItemCount > 0 ? pulled.ItemCount : entity.ItemCount;
entity.TotalAmount = pulled.TotalAmount ?? entity.TotalAmount;
entity.Currency = pulled.Currency ?? entity.Currency;
entity.PlatformOrderTime = pulled.PlatformOrderTime ?? entity.PlatformOrderTime;
entity.PlatformStatus = pulled.PlatformStatus ?? entity.PlatformStatus;
entity.PlatformStatusLabel = pulled.PlatformStatusLabel ?? entity.PlatformStatusLabel;
entity.FulfillStatus = pulled.FulfillStatus;
entity.ShippingLabelNo = pulled.ShippingLabelNo ?? entity.ShippingLabelNo;
entity.ShippingLabelUrl = pulled.ShippingLabelUrl ?? entity.ShippingLabelUrl;
entity.PlatformExtraJson = pulled.ExtraJson ?? entity.PlatformExtraJson;
entity.LastSyncTime = now;
if (entity.Id > 0)
{
await storeOrderRepository.UpdateAsync(entity);
}
else
{
entity = await storeOrderRepository.InsertAsync(entity);
}
if (pulled.Lines.Count == 0)
{
return;
}
var existingLines = await storeOrderLineRepository.GetListAsync(x => x.StoreOrderId == entity.Id);
foreach (var line in existingLines)
{
await storeOrderLineRepository.DeleteAsync(line);
}
foreach (var line in pulled.Lines)
{
await storeOrderLineRepository.InsertAsync(new SalesStoreOrderLine
{
StoreOrderId = entity.Id,
PlatformLineId = line.PlatformLineId,
PlatformSku = line.PlatformSku,
ProductName = line.ProductName,
Quantity = line.Quantity,
UnitPrice = line.UnitPrice,
Currency = line.Currency
});
}
}
private static void ApplyStatusResult(SalesStoreOrder order, PlatformShopOrderStatusResult result)
{
if (!string.IsNullOrWhiteSpace(result.PlatformStatus))
{
order.PlatformStatus = result.PlatformStatus;
}
if (!string.IsNullOrWhiteSpace(result.PlatformStatusLabel))
{
order.PlatformStatusLabel = result.PlatformStatusLabel;
}
if (!string.IsNullOrWhiteSpace(result.FulfillStatus))
{
order.FulfillStatus = result.FulfillStatus;
}
if (!string.IsNullOrWhiteSpace(result.ShippingLabelNo))
{
order.ShippingLabelNo = result.ShippingLabelNo;
}
if (!string.IsNullOrWhiteSpace(result.ShippingLabelUrl))
{
order.ShippingLabelUrl = result.ShippingLabelUrl;
}
}
private async Task<List<SalesStoreOrder>> LoadOrdersAsync(int? customerId, List<int> ids)
{
if (ids.Count == 0)
{
throw new BusinessException("请选择订单");
}
var (hasCustomerFilter, customerIdValue) = WarehouseCustomerHelper.ToCustomerFilter(customerId);
return await storeOrderRepository.GetListAsync(x =>
(!hasCustomerFilter || x.CustomerId == customerIdValue) && ids.Contains(x.Id));
}
private static PlatformStoreOrderBatchActionItemOutput FailItem(SalesStoreOrder order, string message) =>
new()
{
Id = order.Id,
PlatformOrderNo = order.PlatformOrderNo,
Success = false,
Message = message
};
}

View File

@ -34,6 +34,8 @@ public class PlatformStoreOrderService : ApplicationService
private IWarehouseAddressRepository WarehouseAddressRepository => LazyServiceProvider.GetRequiredService<IWarehouseAddressRepository>();
private ISalesProductRepository SalesProductRepository => LazyServiceProvider.GetRequiredService<ISalesProductRepository>();
private OrderService OrderService => LazyServiceProvider.GetRequiredService<OrderService>();
private PlatformStoreOrderFulfillmentService FulfillmentService =>
LazyServiceProvider.GetRequiredService<PlatformStoreOrderFulfillmentService>();
/// <summary>
/// 查询店铺订单列表。
@ -144,60 +146,67 @@ public class PlatformStoreOrderService : ApplicationService
}
/// <summary>
/// 同步店铺订单状态(平台 SDK 占位)。
/// 从平台拉取店铺订单并入库。
/// </summary>
/// <param name="input">入参:店铺、时间范围、平台状态。</param>
/// <returns>拉单结果(按店铺汇总)。</returns>
[HttpPost("/order/platform-store-order/pull-orders")]
public async Task<PlatformStoreOrderBatchActionOutput> PullOrdersAsync(PlatformStoreOrderPullInput input)
{
var customerId = await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync(
CustomerRepository,
CurrentUser,
input.CustomerId);
return await FulfillmentService.PullOrdersAsync(customerId, input);
}
/// <summary>
/// 同步店铺订单状态(从平台刷新选中订单)。
/// </summary>
/// <param name="input">入参:订单 ID 列表。</param>
/// <returns>同步结果提示。</returns>
/// <returns>同步结果(成功/失败明细)。</returns>
[HttpPost("/order/platform-store-order/sync-status")]
public async Task<PlatformStoreOrderActionOutput> SyncStatusAsync(PlatformStoreOrderIdsInput input)
public async Task<PlatformStoreOrderBatchActionOutput> SyncStatusAsync(PlatformStoreOrderIdsInput input)
{
await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync(
var customerId = await WarehouseCustomerHelper.ResolveListCustomerIdAsync(
CustomerRepository,
CurrentUser,
input.CustomerId);
return new PlatformStoreOrderActionOutput
{
Message = "平台订单同步待 SDK 接入,当前为占位实现"
};
return await FulfillmentService.SyncStatusAsync(customerId, input);
}
/// <summary>
/// 店铺订单 RTS 就绪发货(平台 SDK 占位)。
/// 店铺订单 RTS 就绪发货。
/// </summary>
/// <param name="input">入参:订单 ID 列表。</param>
/// <returns>操作结果提示。</returns>
/// <returns>操作结果(成功/失败明细)。</returns>
[HttpPost("/order/platform-store-order/ready-to-ship")]
public async Task<PlatformStoreOrderActionOutput> ReadyToShipAsync(PlatformStoreOrderIdsInput input)
public async Task<PlatformStoreOrderBatchActionOutput> ReadyToShipAsync(PlatformStoreOrderIdsInput input)
{
await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync(
var customerId = await WarehouseCustomerHelper.ResolveListCustomerIdAsync(
CustomerRepository,
CurrentUser,
input.CustomerId);
return new PlatformStoreOrderActionOutput
{
Message = "RTS 待平台 SDK 接入,当前为占位实现"
};
return await FulfillmentService.ReadyToShipAsync(customerId, input);
}
/// <summary>
/// 拉取店铺订单面单(平台 SDK 占位)。
/// 重新拉取店铺订单面单。
/// </summary>
/// <param name="input">入参:订单 ID。</param>
/// <returns>操作结果提示。</returns>
/// <returns>面单号与下载地址。</returns>
[HttpPost("/order/platform-store-order/fetch-label")]
public async Task<PlatformStoreOrderActionOutput> FetchLabelAsync(PlatformStoreOrderIdInput input)
{
await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync(
var customerId = await WarehouseCustomerHelper.ResolveListCustomerIdAsync(
CustomerRepository,
CurrentUser,
input.CustomerId);
return new PlatformStoreOrderActionOutput
{
Message = "面单拉取待平台 SDK 接入,当前为占位实现"
};
return await FulfillmentService.FetchLabelAsync(customerId, input);
}
/// <summary>