harden(epay): cart-hygiene invariant uses confirmed cart count + add service architecture plan
- cartCount tracks actual cart rows (decrement only on confirmed delete) so a failed cleanup delete can't trigger a false dirty-cart abort. - docs/plans/006: the multi-tenant CF-service architecture (DB-backed fulfiller, account pool, catalog dedup, per-tenant credential model, reversible flag flip) — the executable next phase. The Phase-F flag flip is gated on the orchestrator fulfiller existing (Plan 003 Faza F was wrong). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -308,7 +308,10 @@ async function processBatch(
|
||||
// leftover row from a previously-crashed batch would be paid for and
|
||||
// attach the wrong PDF. We track our own basket ids for cleanup, and
|
||||
// bail the moment ANCPI reports more rows than we put in.
|
||||
let addedCount = 0;
|
||||
// cartCount tracks the rows actually in the cart (incremented on add,
|
||||
// decremented only on a CONFIRMED delete) so the invariant stays correct
|
||||
// even if a cleanup delete fails.
|
||||
let cartCount = 0;
|
||||
for (let idx = 0; idx < items.length; idx++) {
|
||||
const item = items[idx]!;
|
||||
const { extractId, input } = item;
|
||||
@@ -316,14 +319,17 @@ async function processBatch(
|
||||
await updateStatus(extractId, "cart");
|
||||
const { basketRowId, numberOfItems, itemIds } =
|
||||
await client.addToCartDetailed(input.prodId ?? 14200);
|
||||
cartCount++;
|
||||
|
||||
// After N successful adds a clean cart reports exactly N items. More
|
||||
// than that = pre-existing junk (orphans from a crash). Never submit a
|
||||
// cart we didn't fully build: wipe everything ANCPI listed and abort —
|
||||
// the next retry starts clean. No charge happens (we never submit).
|
||||
if (numberOfItems > addedCount + 1) {
|
||||
// Right after the add, a clean cart reports exactly cartCount rows.
|
||||
// More than that = pre-existing junk (orphans from a crash). Never
|
||||
// submit a cart we didn't fully build: wipe everything ANCPI listed
|
||||
// and abort — the next retry starts clean. No charge (we never submit).
|
||||
// (numberOfItems falls back to items.length, so an unexpected response
|
||||
// shape degrades to "no excess detected" rather than a false abort.)
|
||||
if (numberOfItems > cartCount) {
|
||||
console.error(
|
||||
`[epay-queue] Dirty cart: expected ${addedCount + 1} rows, ANCPI reports ${numberOfItems}. Wiping + aborting batch.`,
|
||||
`[epay-queue] Dirty cart: expected ${cartCount} rows, ANCPI reports ${numberOfItems}. Wiping + aborting batch.`,
|
||||
);
|
||||
const toWipe = itemIds.length
|
||||
? itemIds
|
||||
@@ -338,7 +344,6 @@ async function processBatch(
|
||||
}
|
||||
|
||||
ourBasketIdsForCleanup.push(basketRowId);
|
||||
addedCount++;
|
||||
item.basketRowId = basketRowId;
|
||||
await updateStatus(extractId, "cart", { basketRowId });
|
||||
|
||||
@@ -380,10 +385,12 @@ async function processBatch(
|
||||
errorMessage: "Salvarea metadatelor în ePay a eșuat.",
|
||||
});
|
||||
// Remove this metadata-less row from the cart so it can't be
|
||||
// checked out and charged. Drop it from our tracking + batch.
|
||||
await client.deleteCartItem(basketRowId, idx);
|
||||
// checked out and charged. Only decrement cartCount if ANCPI
|
||||
// confirmed the delete — otherwise the row is still there and the
|
||||
// invariant must keep counting it.
|
||||
const deleted = await client.deleteCartItem(basketRowId, idx);
|
||||
if (deleted) cartCount--;
|
||||
ourBasketIdsForCleanup.pop();
|
||||
addedCount--;
|
||||
item.basketRowId = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user