fix: TikTok 准备发货改为真实 shipPackage,并回写面单状态
All checks were successful
Build and Deploy / deploy (push) Successful in 1m1s
All checks were successful
Build and Deploy / deploy (push) Successful in 1m1s
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
9c97a218ec
commit
7a611f8cf7
@ -1066,40 +1066,178 @@ internal sealed class PlatformShopFulfillmentClient(
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对齐 WMS ShopordTiktokImpl.send:订单详情取 package_id → 校验 DropOff → shipPackage,再尝试拉面单。
|
||||
/// </summary>
|
||||
private async Task<PlatformShopReadyToShipResult> 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<string, string>
|
||||
{
|
||||
["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<string>();
|
||||
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<string, string>
|
||||
{
|
||||
["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<string, string>
|
||||
{
|
||||
["app_key"] = tiktok.AppKey,
|
||||
["shop_cipher"] = shopCipher,
|
||||
["timestamp"] = PlatformShopCrypto.UnixSeconds().ToString()
|
||||
};
|
||||
var shipBody = new Dictionary<string, object> { ["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<JsonDocument> PostTiktokSignedAsync(
|
||||
PlatformShopOptions options,
|
||||
string accessToken,
|
||||
string path,
|
||||
Dictionary<string, string> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对齐 WMS ShopordTiktokImpl.retryOrderSheet:订单详情取 package_id,再取 SHIPPING_LABEL 并落库。
|
||||
/// </summary>
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user