diff --git a/src/Oms.Application/Integrations/PlatformShop/PlatformShopFulfillmentClient.cs b/src/Oms.Application/Integrations/PlatformShop/PlatformShopFulfillmentClient.cs
index e73b5bd..9617dcd 100644
--- a/src/Oms.Application/Integrations/PlatformShop/PlatformShopFulfillmentClient.cs
+++ b/src/Oms.Application/Integrations/PlatformShop/PlatformShopFulfillmentClient.cs
@@ -1066,40 +1066,178 @@ internal sealed class PlatformShopFulfillmentClient(
};
}
+ ///
+ /// 对齐 WMS ShopordTiktokImpl.send:订单详情取 package_id → 校验 DropOff → shipPackage,再尝试拉面单。
+ ///
private async Task RtsTiktokAsync(
SalesStore store,
PlatformShopOptions options,
string platformOrderNo,
CancellationToken ct)
{
- var sync = await SyncTiktokAsync(store, options, platformOrderNo, ct);
- if (!sync.Success)
+ EnsureTiktok(store, options);
+ var shopCipher = await GetTiktokShopCipherAsync(store, options, ct);
+ if (string.IsNullOrWhiteSpace(shopCipher))
{
- return new PlatformShopReadyToShipResult { Success = false, Message = sync.Message };
+ return new PlatformShopReadyToShipResult { Success = false, Message = "未获取到 TikTok shop_cipher,请重新授权店铺" };
}
- if (!string.Equals(sync.PlatformStatus, "AWAITING_SHIPMENT", StringComparison.OrdinalIgnoreCase)
- && sync.PlatformStatus != "111")
+ var tiktok = options.Tiktok;
+ using var detail = await GetTiktokSignedAsync(options, store.AccessToken!, "/order/202309/orders", new Dictionary
+ {
+ ["app_key"] = tiktok.AppKey,
+ ["shop_cipher"] = shopCipher,
+ ["ids"] = platformOrderNo,
+ ["timestamp"] = PlatformShopCrypto.UnixSeconds().ToString()
+ }, ct);
+ if (!TryGetTiktokData(detail.RootElement, out var data, out var detailError))
+ {
+ return new PlatformShopReadyToShipResult { Success = false, Message = "订单详情信息异常:" + detailError };
+ }
+
+ if (!data.TryGetProperty("orders", out var orders)
+ || orders.ValueKind != JsonValueKind.Array
+ || orders.GetArrayLength() == 0)
+ {
+ return new PlatformShopReadyToShipResult { Success = false, Message = "TikTok 未找到订单" };
+ }
+
+ var order = orders[0];
+ var status = GetString(order, "status") ?? string.Empty;
+ if (string.Equals(status, "CANCELLED", StringComparison.OrdinalIgnoreCase))
+ {
+ return new PlatformShopReadyToShipResult { Success = false, Message = "订单已取消,不可准备发货" };
+ }
+
+ if (status is "IN_TRANSIT" or "DELIVERED" or "COMPLETED"
+ || string.Equals(status, "AWAITING_COLLECTION", StringComparison.OrdinalIgnoreCase))
{
return new PlatformShopReadyToShipResult
{
Success = false,
- Message = $"TikTok 订单状态为 {sync.PlatformStatus},不可 RTS"
+ Message = $"TikTok 订单状态为 {status},不可准备发货"
};
}
+ if (!order.TryGetProperty("line_items", out var lineItems)
+ || lineItems.ValueKind != JsonValueKind.Array
+ || lineItems.GetArrayLength() == 0)
+ {
+ return new PlatformShopReadyToShipResult { Success = false, Message = "商品信息为空" };
+ }
+
+ var packageIds = new List();
+ foreach (var line in lineItems.EnumerateArray())
+ {
+ var packageId = GetString(line, "package_id");
+ if (string.IsNullOrWhiteSpace(packageId)) continue;
+ if (!packageIds.Contains(packageId, StringComparer.Ordinal))
+ {
+ packageIds.Add(packageId);
+ }
+ }
+
+ if (packageIds.Count == 0)
+ {
+ return new PlatformShopReadyToShipResult { Success = false, Message = "订单没有 package_id,无法准备发货" };
+ }
+
+ foreach (var packageId in packageIds)
+ {
+ using var handoverDoc = await GetTiktokSignedAsync(
+ options,
+ store.AccessToken!,
+ $"/fulfillment/202309/packages/{packageId}/handover_time_slots",
+ new Dictionary
+ {
+ ["app_key"] = tiktok.AppKey,
+ ["shop_cipher"] = shopCipher,
+ ["timestamp"] = PlatformShopCrypto.UnixSeconds().ToString()
+ },
+ ct);
+ if (!TryGetTiktokData(handoverDoc.RootElement, out var handoverData, out var handoverError))
+ {
+ return new PlatformShopReadyToShipResult
+ {
+ Success = false,
+ Message = "包裹支持发货类型获异常:" + handoverError
+ };
+ }
+
+ var canDropOff = handoverData.TryGetProperty("can_drop_off", out var drop)
+ && drop.ValueKind is JsonValueKind.True or JsonValueKind.False
+ && drop.GetBoolean();
+ if (!canDropOff)
+ {
+ return new PlatformShopReadyToShipResult { Success = false, Message = "包裹不支持 DropOff 发货" };
+ }
+
+ var shipPath = $"/fulfillment/202309/packages/{packageId}/ship";
+ var shipQuery = new Dictionary
+ {
+ ["app_key"] = tiktok.AppKey,
+ ["shop_cipher"] = shopCipher,
+ ["timestamp"] = PlatformShopCrypto.UnixSeconds().ToString()
+ };
+ var shipBody = new Dictionary { ["handover_method"] = "DROP_OFF" };
+ using var shipDoc = await PostTiktokSignedAsync(options, store.AccessToken!, shipPath, shipQuery, shipBody, ct);
+ // ship 成功时 data 可能为 null,只认 code=0
+ var shipCode = GetString(shipDoc.RootElement, "code");
+ if (shipCode is not ("0" or null))
+ {
+ var msg = GetString(shipDoc.RootElement, "message") ?? "发货失败";
+ return new PlatformShopReadyToShipResult { Success = false, Message = "发货异常:" + msg };
+ }
+ }
+
+ string? labelNo = null;
+ string? labelUrl = LabelPendingText;
+ var labelPending = true;
+ try
+ {
+ var label = await LabelTiktokAsync(store, options, platformOrderNo, ct);
+ if (label.Success)
+ {
+ labelNo = label.ShippingLabelNo;
+ if (!string.IsNullOrWhiteSpace(label.ShippingLabelUrl))
+ {
+ labelUrl = label.ShippingLabelUrl;
+ labelPending = false;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogWarning(ex, "TikTok 准备发货后拉面单失败:{OrderNo}", platformOrderNo);
+ }
+
return new PlatformShopReadyToShipResult
{
Success = true,
- Message = "TikTok RTS 已提交,面单获取中",
+ Message = labelPending ? "准备发货成功,面单获取中" : "准备发货成功",
PlatformStatus = "AWAITING_COLLECTION",
PlatformStatusLabel = "AWAITING_COLLECTION",
FulfillStatus = "toShip",
- ShippingLabelUrl = LabelPendingText,
- LabelPending = true
+ ShippingLabelNo = labelNo,
+ ShippingLabelUrl = labelUrl,
+ LabelPending = labelPending
};
}
+ private async Task PostTiktokSignedAsync(
+ PlatformShopOptions options,
+ string accessToken,
+ string path,
+ Dictionary query,
+ object body,
+ CancellationToken ct)
+ {
+ var bodyJson = JsonSerializer.Serialize(body);
+ var sign = PlatformShopCrypto.TiktokSign(path, query, bodyJson, options.Tiktok.AppSecret);
+ var url = $"{options.Tiktok.HostApi}{path}?{BuildTiktokQuery(query, sign)}";
+ return await PostRawJsonAsync(url, bodyJson, ct, accessToken);
+ }
+
///
/// 对齐 WMS ShopordTiktokImpl.retryOrderSheet:订单详情取 package_id,再取 SHIPPING_LABEL 并落库。
///
diff --git a/src/Oms.Application/Services/Orders/PlatformStoreOrderFulfillmentService.cs b/src/Oms.Application/Services/Orders/PlatformStoreOrderFulfillmentService.cs
index 7f9bc3a..6b4a8dd 100644
--- a/src/Oms.Application/Services/Orders/PlatformStoreOrderFulfillmentService.cs
+++ b/src/Oms.Application/Services/Orders/PlatformStoreOrderFulfillmentService.cs
@@ -280,6 +280,11 @@ internal sealed class PlatformStoreOrderFulfillmentService(
{
order.ShippingLabelNo = result.ShippingLabelNo;
}
+ else if (result.LabelPending)
+ {
+ // 对齐 WMS:准备发货后先标记面单待获取
+ order.ShippingLabelNo = "系统获取中";
+ }
if (!string.IsNullOrWhiteSpace(result.ShippingLabelUrl))
{
@@ -288,6 +293,36 @@ internal sealed class PlatformStoreOrderFulfillmentService(
order.LastSyncTime = DateTime.Now;
await storeOrderRepository.UpdateAsync(order);
+
+ // TikTok:发货后再拉详情,回写运单号等;面单未就绪时保留「系统获取中」
+ if (string.Equals(order.PlatformCode, "tiktok", StringComparison.OrdinalIgnoreCase))
+ {
+ try
+ {
+ var detail = await fulfillmentClient.FetchOrderDetailAsync(store, order.PlatformOrderNo);
+ if (detail != null)
+ {
+ if (result.LabelPending && string.IsNullOrWhiteSpace(detail.ShippingLabelUrl))
+ {
+ detail.ShippingLabelUrl = "系统获取中";
+ detail.ShippingLabelNo ??= "系统获取中";
+ }
+
+ detail.PlatformStatus ??= "AWAITING_COLLECTION";
+ detail.PlatformStatusLabel ??= detail.PlatformStatus;
+ detail.FulfillStatus = string.IsNullOrWhiteSpace(detail.FulfillStatus)
+ ? "toShip"
+ : detail.FulfillStatus;
+ await UpsertPulledOrderAsync(order.CustomerId, store, detail);
+ order = await storeOrderRepository.FirstOrDefaultAsync(x => x.Id == order.Id) ?? order;
+ }
+ }
+ catch
+ {
+ // 详情回写失败不影响 RTS 成功结果
+ }
+ }
+
return new PlatformStoreOrderBatchActionItemOutput
{
Id = order.Id,