fix: harden Shopify tracking sync for closed fulfillments

Make supplier-side Shopify sync update tracking on existing fulfillments when fulfillment orders are closed, and make EC2 deploy extraction deletion-safe to prevent stale files from breaking builds.

Made-with: Cursor
This commit is contained in:
talatops 2026-03-31 22:05:33 +05:00
parent b67ef3190c
commit bcd479b76a
2 changed files with 89 additions and 6 deletions

View File

@ -53,8 +53,17 @@ jobs:
rm -rf "./prisma/init-pgvector.sql"
fi
# Unpack latest code snapshot over deploy directory
unzip -oq deploy.zip -d .
# Clean, deletion-safe deploy:
# extract to temp dir, then rsync with --delete so removed files are purged.
rm -rf .deploy_extract_tmp
mkdir -p .deploy_extract_tmp
unzip -oq deploy.zip -d .deploy_extract_tmp
rsync -a --delete \
--exclude ".env" \
--exclude "deploy.zip" \
--exclude ".deploy_extract_tmp" \
.deploy_extract_tmp/ ./
rm -rf .deploy_extract_tmp
rm -f deploy.zip
# Recreate .env from secret for runtime values

View File

@ -96,6 +96,64 @@ async function syncOrderToShopify(
}
}
const updateTrackingOnExistingFulfillment = async (): Promise<{ ok: boolean; error?: string }> => {
const listRes = await fetch(
`https://${cleanDomain}/admin/api/2024-01/orders/${shopifyOrderId}/fulfillments.json`,
{
method: "GET",
headers: {
"X-Shopify-Access-Token": store.shopifyAccessToken,
"Content-Type": "application/json",
},
},
)
if (!listRes.ok) {
const details = await listRes.text().catch(() => "Unknown Shopify response")
return { ok: false, error: `Read existing fulfillments failed: ${details}` }
}
const listPayload = (await listRes.json()) as {
fulfillments?: Array<{ id: number; created_at?: string }>
}
const fulfillments = listPayload.fulfillments || []
if (fulfillments.length === 0) {
return { ok: false, error: "No existing fulfillment found to update tracking." }
}
const sorted = [...fulfillments].sort((a, b) => {
const aTs = a.created_at ? new Date(a.created_at).getTime() : 0
const bTs = b.created_at ? new Date(b.created_at).getTime() : 0
return bTs - aTs
})
const latest = sorted[0]
const updateRes = await fetch(
`https://${cleanDomain}/admin/api/2024-01/fulfillments/${latest.id}/update_tracking.json`,
{
method: "POST",
headers: {
"X-Shopify-Access-Token": store.shopifyAccessToken,
"Content-Type": "application/json",
},
body: JSON.stringify({
fulfillment: {
notify_customer: false,
tracking_info: {
number: trackingNumber,
company: trackingCompany,
url: trackingUrl || undefined,
},
},
}),
},
)
if (!updateRes.ok) {
const details = await updateRes.text().catch(() => "Unknown Shopify response")
return { ok: false, error: `Update tracking on existing fulfillment failed: ${details}` }
}
return { ok: true }
}
const createLegacyFulfillmentFallback = async (): Promise<{ ok: boolean; error?: string }> => {
const orderRes = await fetch(
`https://${cleanDomain}/admin/api/2024-01/orders/${shopifyOrderId}.json?status=any`,
@ -173,10 +231,24 @@ async function syncOrderToShopify(
}
}
const foJson = (await foRes.json()) as { fulfillment_orders?: Array<{ id: number }> }
const fulfillmentOrders = foJson.fulfillment_orders || []
const foJson = (await foRes.json()) as {
fulfillment_orders?: Array<{ id: number; status?: string; request_status?: string }>
}
const allFulfillmentOrders = foJson.fulfillment_orders || []
const fulfillmentOrders = allFulfillmentOrders.filter((fo) => {
const status = String(fo.status || "").toLowerCase()
// Only try fulfillment orders that are still actionable.
return status && status !== "closed" && status !== "cancelled" && status !== "incomplete"
})
if (fulfillmentOrders.length === 0) {
return { ok: false, error: "No fulfillment orders available for this order." }
const closedCount = allFulfillmentOrders.filter((fo) => String(fo.status || "").toLowerCase() === "closed").length
if (closedCount > 0) {
// Closed fulfillment orders are usually already fulfilled: update tracking on existing fulfillment.
const updated = await updateTrackingOnExistingFulfillment()
if (updated.ok) return updated
return { ok: false, error: `All fulfillment orders are closed. ${updated.error}` }
}
return { ok: false, error: "No actionable fulfillment orders available for this order." }
}
const payload = {
@ -203,12 +275,14 @@ async function syncOrderToShopify(
})
if (!syncRes.ok) {
const details = await syncRes.text().catch(() => "Unknown Shopify response")
const updated = await updateTrackingOnExistingFulfillment()
if (updated.ok) return updated
const fallback = await createLegacyFulfillmentFallback()
if (fallback.ok) return fallback
const scopes = await fetchScopes()
return {
ok: false,
error: `Shopify fulfillment sync failed: ${details}. Fallback also failed: ${fallback.error}. Token scopes: [${scopes.join(", ")}]. Re-install/reconnect app after scope changes.`,
error: `Shopify fulfillment sync failed: ${details}. Existing fulfillment tracking update failed: ${updated.error}. Fallback also failed: ${fallback.error}. Token scopes: [${scopes.join(", ")}]. Re-install/reconnect app after scope changes.`,
}
}