Dubbele factuur-mail door race-conditie in finalizeAcceptedQuote #96

Closed
opened 2026-05-26 18:23:31 +00:00 by jesse-a · 1 comment
Owner

Severity: MEDIUM (data-integriteit)

finalizeAcceptedQuote() in src/lib/quote-finalize.ts had if (!invoice.emailedAt) { send; update } — non-atomair. Tussen check en update zat een 5-10s window (SMTP-send + Mollie-API). Twee parallel-aanroepen konden allebei door de check heen → klant kreeg dezelfde factuur 2× per mail.

Triggers in productie:

  1. DocuSeal-webhook-retries (slow webhook → DocuSeal denkt timeout, vuurt opnieuw)
  2. Webhook + handmatige sync-knop vrijwel tegelijk

Fix: prisma.invoice.updateMany({ where: { id, emailedAt: null }, data: { emailedAt: now } }). DB doet 1 UPDATE die maximaal 1 rij raakt. Zelfde patroon toegepast op payment-reminders-cron.

Files: src/lib/quote-finalize.ts, src/app/api/cron/payment-reminders/route.ts

**Severity: MEDIUM** (data-integriteit) `finalizeAcceptedQuote()` in `src/lib/quote-finalize.ts` had `if (!invoice.emailedAt) { send; update }` — non-atomair. Tussen check en update zat een 5-10s window (SMTP-send + Mollie-API). Twee parallel-aanroepen konden allebei door de check heen → klant kreeg dezelfde factuur 2× per mail. Triggers in productie: 1. DocuSeal-webhook-retries (slow webhook → DocuSeal denkt timeout, vuurt opnieuw) 2. Webhook + handmatige sync-knop vrijwel tegelijk **Fix**: `prisma.invoice.updateMany({ where: { id, emailedAt: null }, data: { emailedAt: now } })`. DB doet 1 UPDATE die maximaal 1 rij raakt. Zelfde patroon toegepast op payment-reminders-cron. **Files**: src/lib/quote-finalize.ts, src/app/api/cron/payment-reminders/route.ts
Author
Owner

Opgelost in commit ec4baeb.

Opgelost in commit ec4baeb.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
jesse-a/OpenCRM#96
No description provided.