Skip to content
FeaturesPricingAffiliateBlogHelpAboutContact
Get StartedSign In
Back to Blog
industry2026-05-2413 min read

My card hit 3DS, thMenu showed grace period — Stripe invoice.payment_action_required gap (PR #606 CCC F4)

Asdis (44) owns Kjotsupan 32-cover modern Icelandic comfort-food cafe Reykjavik Skolavordustigur two blocks downhill from Hallgrimskirkja. 6 years, Pro tier $29/month Stripe auto-recurring. Tuesday 06:42 UTC phone buzzed: "thMenu Your subscription is now in GRACE PERIOD. Payment failed. Update within 7 days." Same time Arion Bank SMS: "$29 thMenu payment requires 3D-Secure verification confirm in Arion app." Asdis Arion app push Approve + FaceID 3DS verification successful. 5min later thMenu still GRACE PERIOD. EU PSD2 SCA 2019 mandates 3DS challenges. Stripe payment flow on 3DS sends invoice.payment_action_required webhook event customer action required temporary hold tier untouched. Asdis flow: 06:30 charge attempt, 06:31 Arion 3DS required + Stripe invoice.payment_action_required webhook → thMenu default branch silent 200 OK, 06:42 thMenu invoice.payment_failed logic mistakenly fired GRACE PERIOD email (action_required != payment_failed conflated), 06:43 Asdis 3DS complete + Stripe invoice.paid → thMenu tier active. Email confusion: action_required and payment_failed conflated. Engineering forensic apps/web-admin/src/app/api/stripe/webhook/route.ts switch event.type 6 cases handled: checkout.session.completed + customer.subscription.updated + customer.subscription.deleted + invoice.paid + invoice.payment_failed + charge.dispute.created. **3 modern Customer Portal lifecycle events missing**: (1) invoice.payment_action_required 3DS/SCA Asdis case; (2) customer.subscription.paused Stripe Customer Portal pause feature tier still paid; (3) customer.subscription.resumed pre-pause tier instead of current price_id re-derive. 90-day Stripe dashboard delivered events grouped by type: **847 missing-handler events** 612 invoice.payment_action_required + 178 customer.subscription.paused + 57 customer.subscription.resumed ~2% error rate every event customer-affecting. **PR #606 batch CCC F4** 4-layer fix: **Layer 1** invoice.payment_action_required DLT info row + sendActionRequiredEmail "complete 3DS challenge in bank app" + Stripe hosted invoice link, **NO tier change** temporary state. **Layer 2** customer.subscription.paused tier = starter + cascadeTierDowngrade (PR #519 EE) + DLT info row. **Layer 3** customer.subscription.resumed deriveTierFromPriceId current price (not stale pre-pause — customer may have upgraded/downgraded during pause) + syncD1Tier. **Layer 4** default branch [BEACON:stripe_webhook_unhandled] Sentry beacon + DLT info — instead of silent 200 surface coverage gap loudly. Asdis response 12 hours 1-month Pro tier credit + proper email going forward. 847 backfill: 612 action_required cannot be retro-replayed (Stripe delivered) but super-admin dashboard DLT-replay button enabled + 360-day Pro discount coupon affected restaurants. 178 paused 23 stale-entitlement compassionate gift policy. 57 resumed 12 pause-period upgraded tier backfilled. Sevda Malatya Inönü Cad. "Malatya Kayısı + Kebap" 50-cover Eastern Anatolian cuisine version with same flow VakıfBank 3DS. Pattern: **Stripe webhook handlers must explicitly handle every event type in modern Customer Portal lifecycle; default branch must emit [BEACON:stripe_webhook_unhandled] Sentry alert not silent 200 OK so coverage gaps surface loudly.** Implementation checklist: (1) switch explicit cases lifecycle events; (2) action_required DLT + email NO tier change; (3) paused tier=starter + cascade + DLT; (4) resumed price_id re-derive; (5) trial_will_end customer email; (6) default branch Sentry beacon + DLT info; (7) quarterly Stripe dashboard audit; (8) customer-side UX action_required vs failed differentiation.

th

thMenu Team

thmenu.com

Found this helpful? Share it.