I got two loyalty discount codes for my anniversary bonus applyLoyalty cron duplicate belt-and-suspenders — QQ-B F2 (PR #553)
Eilidh Glasgow West End Byres Road 42-yo 11-yr Glasgow Old Town Brewery 50-cover Scottish ale + craft beer pub serving locals + Glasgow University students + Kelvingrove tourists thMenu Pro 13 months anniversary celebration. Pro tier subscribers month 12 one-time thMenu Anniversary Bonus 10% Stripe discount code generated. Eilidh inbox two separate thMenu Loyalty Bonus emails arrived 08:47 + 08:49 UTC codes EIL-ANNIV-Q4R7P + EIL-ANNIV-X9M2B. Stripe Customer Portal opened first applied 10% second revoked. Two codes generated only one should be valid. Support contacted. Engineering 6-month applyLoyalty cron log audit 28 user_ids two active loyalty_discounts rows 56 Stripe promotion_codes half post-hoc revoked. Forensic cloudflare/src/cron-jobs/apply-loyalty.ts cron every 30-min Pro+ subscribers user_id 12 months completed eligible SELECT WHERE pro_started_at < NOW() - 12 months AND no_loyalty_row pseudo-code Stripe API POST promotion_codes idempotency-key floor(Date.now() / 86400000 / 30) based epoch_month boundary. Epoch month boundary edge case floor(Date.now() / 86400000 / 30) flips between X and X+1 within minutes between cron ticks. Two parallel ticks different idempotency-keys Stripe creates two separate promotion_codes. 3 wrong theories (1) Stripe API same payload duplicate sense Stripe Idempotency-Key expects inferred dedup absent; (2) Supabase INSERT loyalty_discounts UNIQUE constraint not enough ON CONFLICT IGNORE INSERT blocked cron exception throw retry then promotion_code Stripe duplicate already created; (3) cron_idempotency_claims wrap separate ticks blocks same day but one tick claim acquired crashed monthly_epoch previous month new month claim cleared. Correct pattern Stripe Idempotency-Key stable user_id + resource_id base 24h Stripe server-side dedup + Supabase loyalty_discounts partial UNIQUE WHERE status IN (applied, skipped) DB belt-and-suspenders + applyLoyalty cron try/catch 23505 unique_violation continue. PR #553 batch QQ-B F2 2-layer fix Layer 1 Supabase migration 20260523000003_loyalty_discounts_uniq CREATE UNIQUE INDEX uq_loyalty_discounts_active ON loyalty_discounts(user_id) WHERE status IN (applied, skipped) revoked exempt legitimate cancel + resubscribe flow earn fresh loyalty row. Layer 2 applyLoyalty cron try/catch PostgreSQL 23505 unique_violation continue two ticks INSERT race UNIQUE catch + bonus pre-flight Supabase SELECT loyalty_discounts WHERE user_id + status IN (applied, skipped) maybeSingle Stripe API call front block quota burn Stripe promotion_codes 200 promo code/account-limit free dollars Stripe. Production audit + backfill Eilidh first code (EIL-ANNIV-Q4R7P) valid second code (EIL-ANNIV-X9M2B) Stripe Revocations API + Supabase status='revoked' patch + email apology + 1-month Pro tier credit. 28 users 56 Stripe promotion_codes 28 revoked production-wide audit + apology + credit. Post-deploy 60-day 0 new duplicate loyalty_discounts rows applyLoyalty cron parallel tick UNIQUE constraint catch 23505 continue Stripe API never called duplicate quota saved. Huseyin Sivas Merkez Cumhuriyet Caddesi 38-yo 15-yr Sivas Koftesi + Kelle Paca 40-cover traditional Sivas kofte + 7-hour-cooked sheep head soup parallel pattern two discount codes codes HSY-ANNIV-A2X9F + HSY-ANNIV-B7K3L same fix shipped 1-month Pro credit. Pattern Cloudflare cron at-least-once delivery external API write zero idempotency = orphan external state Stripe POST promotion_codes Resend email send Wise transfer BOTH idempotency-key header AND DB-side UNIQUE constraint required one alone insufficient. Sibling sweep applyLoyalty QQ-B F2 + scanFor1099Threshold PR #536 LL + aff-postback-dispatch PR #493 R + order-refund PR #311 + #328 + customer-magic-link PR #335 + Stripe webhook checkout PR #313 + #519. Implementation pattern (1) stable Stripe Idempotency-Key user_id + resource_id 24h server-side dedup, (2) Supabase/D1 partial UNIQUE constraint belt-and-suspenders, (3) INSERT error catch 23505 collapses already_processed/replay branch. PR #553 reference.
thMenu Team
thmenu.com
Found this helpful? Share it.
Related articles
Why Digital Menus Increase Restaurant Revenue by Up to 30%
Studies show restaurants using digital QR menus see measurable increases in aver…
When a Customer Downgrades, What Happens to Old Features? — The Silent Feature-Drift Problem in SaaS
Most SaaS apps run a single line of code when a customer downgrades — but old fe…
JWT alg-confusion attack — why Supabase's HS256 → RS256/JWKS migration breaks legacy verifiers
Verifiers that never decode the JWT header are wide open to `alg=none` and alg-c…