fix(epay): CRITICAL multi-item batch regressions — wrong basketRowId + stale order match
Found via a real 2-item batch (280067 + 327649) on 2026-06-05 that produced a wrong PDF (correctly caught as "De verificat" by the R4 safety net) and a failed download: 1. addToCartDetailed took items[items.length-1], but ePay returns the cart NEWEST-FIRST, so the just-added row is items[0]. On a 2+ item batch every add reported the OLDEST row's id → two rows collapsed onto one basketRowId → metadata saved to the wrong row → broken cart. Single-item orders were unaffected (one element). Reverted to items[0]. 2. findNewOrderId accepted any id != previousOrderId, so when our submit created nothing it adopted an unrelated OLDER order (yesterday's 10009605) and attached its 15 Feleacu PDFs to today's parcels. ePay order numbers are sequential, so a genuinely-new order must be numerically GREATER than the latest pre-submit order; otherwise fail (recoverable) instead of matching a stale order. Take the highest genuinely-new id. Removed the now-dead latest-id fallback. The R4 "review" flag did its job — the wrong PDF was flagged for verification, never shown as valid. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -233,9 +233,11 @@ export class EpayClient {
|
|||||||
|
|
||||||
const data = response.data as EpayCartResponse;
|
const data = response.data as EpayCartResponse;
|
||||||
const items = Array.isArray(data?.items) ? data.items : [];
|
const items = Array.isArray(data?.items) ? data.items : [];
|
||||||
// The freshly added row is the one we didn't know about; ePay returns
|
// ePay returns the full cart NEWEST-FIRST, so the just-added row is
|
||||||
// the full cart in `items`, newest typically last. Be defensive.
|
// items[0]. (Taking items[last] broke 2+ item batches: every add
|
||||||
const added = items[items.length - 1] ?? items[0];
|
// reported the OLDEST row's id, so two rows collapsed onto one
|
||||||
|
// basketRowId and metadata was saved to the wrong row — 2026-06-05.)
|
||||||
|
const added = items[0];
|
||||||
if (!added?.id) {
|
if (!added?.id) {
|
||||||
throw new Error(`ePay addToCart failed: ${JSON.stringify(data).slice(0, 200)}`);
|
throw new Error(`ePay addToCart failed: ${JSON.stringify(data).slice(0, 200)}`);
|
||||||
}
|
}
|
||||||
@@ -604,28 +606,34 @@ export class EpayClient {
|
|||||||
});
|
});
|
||||||
const html = String(response.data ?? "");
|
const html = String(response.data ?? "");
|
||||||
|
|
||||||
// Find ALL orderIds on the page
|
// ePay order numbers are sequential, so a genuinely NEW order is always
|
||||||
|
// numerically GREATER than the latest order that existed before submit.
|
||||||
|
// Requiring oid > previousOrderId is what stops us from adopting an
|
||||||
|
// unrelated OLD order when our submit didn't actually create one — the
|
||||||
|
// "!= previousOrderId" check alone let an older id through (2026-06-05:
|
||||||
|
// a new batch grabbed yesterday's order 10009605 and attached its PDFs).
|
||||||
|
const prevNum = previousOrderId ? Number(previousOrderId) : 0;
|
||||||
|
const isGenuinelyNew = (oid: string): boolean =>
|
||||||
|
!!oid &&
|
||||||
|
oid !== previousOrderId &&
|
||||||
|
!knownOrderIds?.has(oid) &&
|
||||||
|
(!Number.isFinite(prevNum) || prevNum === 0 || Number(oid) > prevNum);
|
||||||
|
|
||||||
|
// Find ALL orderIds on the page; take the highest genuinely-new one.
|
||||||
const allMatches = html.matchAll(/ShowOrderDetails\.action\?orderId=(\d+)/g);
|
const allMatches = html.matchAll(/ShowOrderDetails\.action\?orderId=(\d+)/g);
|
||||||
|
let best = "";
|
||||||
for (const m of allMatches) {
|
for (const m of allMatches) {
|
||||||
const oid = m[1] ?? "";
|
const oid = m[1] ?? "";
|
||||||
if (!oid) continue;
|
if (!isGenuinelyNew(oid)) continue;
|
||||||
if (oid === previousOrderId) continue;
|
if (!best || Number(oid) > Number(best)) best = oid;
|
||||||
if (knownOrderIds?.has(oid)) continue;
|
}
|
||||||
console.log(`[epay] New orderId: ${oid}`);
|
if (best) {
|
||||||
return oid;
|
console.log(`[epay] New orderId: ${best}`);
|
||||||
}
|
return best;
|
||||||
|
|
||||||
// If no new orderId found, the latest one might be it (first order) —
|
|
||||||
// but NEVER adopt the previous/known order: after a submit that timed
|
|
||||||
// out without creating anything, returning the stale id would attach
|
|
||||||
// the wrong order and download its old documents.
|
|
||||||
const latest = html.match(/ShowOrderDetails\.action\?orderId=(\d+)/);
|
|
||||||
const latestId = latest?.[1];
|
|
||||||
if (latestId && latestId !== previousOrderId && !knownOrderIds?.has(latestId)) {
|
|
||||||
console.log(`[epay] Using latest orderId: ${latestId}`);
|
|
||||||
return latestId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No genuinely-new order on the dashboard → the submit created nothing.
|
||||||
|
// Fail (recoverable) rather than adopting a stale/previous/known order.
|
||||||
throw new Error("Could not determine orderId after checkout");
|
throw new Error("Could not determine orderId after checkout");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user