fix(ancpi): find NEW orderId after submit, track known IDs in queue
submitOrder now captures the previous orderId BEFORE submitting, then searches for a NEW orderId that isn't in the knownOrderIds set. Queue passes knownOrderIds between sequential items to prevent duplicate orderId assignment (unique constraint violation). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -434,11 +434,22 @@ export class EpayClient {
|
|||||||
|
|
||||||
/* ── Order Submission ──────────────────────────────────────── */
|
/* ── Order Submission ──────────────────────────────────────── */
|
||||||
|
|
||||||
async submitOrder(): Promise<string> {
|
/**
|
||||||
|
* Submit order. Pass knownOrderIds to skip them when finding the new orderId.
|
||||||
|
*/
|
||||||
|
async submitOrder(knownOrderIds?: Set<string>): Promise<string> {
|
||||||
|
// Get current latest orderId BEFORE submitting (to detect the new one)
|
||||||
|
let previousOrderId: string | null = null;
|
||||||
|
try {
|
||||||
|
previousOrderId = await this.getLatestOrderId();
|
||||||
|
} catch {
|
||||||
|
// No previous orders
|
||||||
|
}
|
||||||
|
|
||||||
const body = new URLSearchParams();
|
const body = new URLSearchParams();
|
||||||
body.set("goToCheckout", "true");
|
body.set("goToCheckout", "true");
|
||||||
|
|
||||||
await this.client.post(
|
const response = await this.client.post(
|
||||||
`${BASE_URL}/EditCartSubmit.action`,
|
`${BASE_URL}/EditCartSubmit.action`,
|
||||||
body.toString(),
|
body.toString(),
|
||||||
{
|
{
|
||||||
@@ -449,7 +460,12 @@ export class EpayClient {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return this.getLatestOrderId();
|
const finalUrl =
|
||||||
|
response.request?.res?.responseUrl ?? response.config?.url ?? "";
|
||||||
|
console.log(`[epay] EditCartSubmit: finalUrl=${finalUrl.slice(0, 100)}`);
|
||||||
|
|
||||||
|
// Find the NEW orderId (different from previous + not in knownOrderIds)
|
||||||
|
return this.findNewOrderId(previousOrderId, knownOrderIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getLatestOrderId(): Promise<string> {
|
private async getLatestOrderId(): Promise<string> {
|
||||||
@@ -459,6 +475,36 @@ export class EpayClient {
|
|||||||
const html = String(response.data ?? "");
|
const html = String(response.data ?? "");
|
||||||
const match = html.match(/ShowOrderDetails\.action\?orderId=(\d+)/);
|
const match = html.match(/ShowOrderDetails\.action\?orderId=(\d+)/);
|
||||||
if (match) return match[1] ?? "";
|
if (match) return match[1] ?? "";
|
||||||
|
throw new Error("Could not determine orderId");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async findNewOrderId(
|
||||||
|
previousOrderId: string | null,
|
||||||
|
knownOrderIds?: Set<string>,
|
||||||
|
): Promise<string> {
|
||||||
|
const response = await this.client.get(`${BASE_URL}/LogIn.action`, {
|
||||||
|
timeout: DEFAULT_TIMEOUT_MS,
|
||||||
|
});
|
||||||
|
const html = String(response.data ?? "");
|
||||||
|
|
||||||
|
// Find ALL orderIds on the page
|
||||||
|
const allMatches = html.matchAll(/ShowOrderDetails\.action\?orderId=(\d+)/g);
|
||||||
|
for (const m of allMatches) {
|
||||||
|
const oid = m[1] ?? "";
|
||||||
|
if (!oid) continue;
|
||||||
|
if (oid === previousOrderId) continue;
|
||||||
|
if (knownOrderIds?.has(oid)) continue;
|
||||||
|
console.log(`[epay] New orderId: ${oid}`);
|
||||||
|
return oid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no new orderId found, the latest one might be it (first order)
|
||||||
|
const latest = html.match(/ShowOrderDetails\.action\?orderId=(\d+)/);
|
||||||
|
if (latest?.[1]) {
|
||||||
|
console.log(`[epay] Using latest orderId: ${latest[1]}`);
|
||||||
|
return latest[1];
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error("Could not determine orderId after checkout");
|
throw new Error("Could not determine orderId after checkout");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -124,10 +124,14 @@ async function processQueue(): Promise<void> {
|
|||||||
if (g.__epayQueueProcessing) return; // already running
|
if (g.__epayQueueProcessing) return; // already running
|
||||||
g.__epayQueueProcessing = true;
|
g.__epayQueueProcessing = true;
|
||||||
|
|
||||||
|
// Track all orderIds from this batch to avoid duplicates
|
||||||
|
const knownOrderIds = new Set<string>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while (g.__epayQueue && g.__epayQueue.length > 0) {
|
while (g.__epayQueue && g.__epayQueue.length > 0) {
|
||||||
const item = g.__epayQueue.shift()!;
|
const item = g.__epayQueue.shift()!;
|
||||||
await processItem(item);
|
const orderId = await processItem(item, knownOrderIds);
|
||||||
|
if (orderId) knownOrderIds.add(orderId);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
g.__epayQueueProcessing = false;
|
g.__epayQueueProcessing = false;
|
||||||
@@ -145,7 +149,10 @@ async function updateStatus(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processItem(item: QueueItem): Promise<void> {
|
async function processItem(
|
||||||
|
item: QueueItem,
|
||||||
|
knownOrderIds: Set<string>,
|
||||||
|
): Promise<string | null> {
|
||||||
const { extractId, input } = item;
|
const { extractId, input } = item;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -155,7 +162,7 @@ async function processItem(item: QueueItem): Promise<void> {
|
|||||||
await updateStatus(extractId, "failed", {
|
await updateStatus(extractId, "failed", {
|
||||||
errorMessage: "Nu ești conectat la ePay.",
|
errorMessage: "Nu ești conectat la ePay.",
|
||||||
});
|
});
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = await EpayClient.create(creds.username, creds.password);
|
const client = await EpayClient.create(creds.username, creds.password);
|
||||||
@@ -167,7 +174,7 @@ async function processItem(item: QueueItem): Promise<void> {
|
|||||||
await updateStatus(extractId, "failed", {
|
await updateStatus(extractId, "failed", {
|
||||||
errorMessage: `Credite insuficiente: ${credits}. Reîncărcați contul pe epay.ancpi.ro.`,
|
errorMessage: `Credite insuficiente: ${credits}. Reîncărcați contul pe epay.ancpi.ro.`,
|
||||||
});
|
});
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Add to cart
|
// Step 2: Add to cart
|
||||||
@@ -216,10 +223,10 @@ async function processItem(item: QueueItem): Promise<void> {
|
|||||||
await updateStatus(extractId, "failed", {
|
await updateStatus(extractId, "failed", {
|
||||||
errorMessage: "Salvarea metadatelor în ePay a eșuat.",
|
errorMessage: "Salvarea metadatelor în ePay a eșuat.",
|
||||||
});
|
});
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderId = await client.submitOrder();
|
const orderId = await client.submitOrder(knownOrderIds);
|
||||||
|
|
||||||
await updateStatus(extractId, "polling", { orderId });
|
await updateStatus(extractId, "polling", { orderId });
|
||||||
|
|
||||||
@@ -242,7 +249,7 @@ async function processItem(item: QueueItem): Promise<void> {
|
|||||||
epayStatus: finalStatus.status,
|
epayStatus: finalStatus.status,
|
||||||
errorMessage: `Comanda ${finalStatus.status.toLowerCase()}.`,
|
errorMessage: `Comanda ${finalStatus.status.toLowerCase()}.`,
|
||||||
});
|
});
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 6: Download PDF
|
// Step 6: Download PDF
|
||||||
@@ -254,7 +261,7 @@ async function processItem(item: QueueItem): Promise<void> {
|
|||||||
epayStatus: finalStatus.status,
|
epayStatus: finalStatus.status,
|
||||||
errorMessage: "Nu s-a găsit documentul PDF în comanda finalizată.",
|
errorMessage: "Nu s-a găsit documentul PDF în comanda finalizată.",
|
||||||
});
|
});
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateStatus(extractId, "downloading", {
|
await updateStatus(extractId, "downloading", {
|
||||||
@@ -303,9 +310,12 @@ async function processItem(item: QueueItem): Promise<void> {
|
|||||||
console.log(
|
console.log(
|
||||||
`[epay-queue] Completed: ${input.nrCadastral} → ${path} (credits: ${newCredits})`,
|
`[epay-queue] Completed: ${input.nrCadastral} → ${path} (credits: ${newCredits})`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return orderId;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : "Eroare necunoscută";
|
const message = error instanceof Error ? error.message : "Eroare necunoscută";
|
||||||
console.error(`[epay-queue] Failed: ${input.nrCadastral}:`, message);
|
console.error(`[epay-queue] Failed: ${input.nrCadastral}:`, message);
|
||||||
await updateStatus(extractId, "failed", { errorMessage: message });
|
await updateStatus(extractId, "failed", { errorMessage: message });
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user