+
Over 7 days old — likely inaccurate ({{ outdated.length }})
+
+
+
diff --git a/todo.md b/todo.md
new file mode 100644
index 0000000..e915903
--- /dev/null
+++ b/todo.md
@@ -0,0 +1,54 @@
+# Todo
+
+Working checklist. Add sections per area. Tick boxes as you go.
+
+---
+
+## Stripe
+
+Spec: `docs/superpowers/specs/2026-04-23-stripe-subscription-lifecycle-design.md`
+Rules: `.claude/rules/payments.md`
+
+### Pre-production (test mode first, then repeat in live mode)
+
+- [ ] **Stripe Dashboard — retry schedule.** Billing → Automations → Subscription retry rules. Switch Smart Retries → Custom. Retry on days 1, 3, 5. After final retry → *Cancel subscription*.
+- [ ] **Stripe Dashboard — customer emails.** Emails → Customer emails. Enable "Successful payments", "Failed payments", "Upcoming renewals".
+- [ ] **Stripe Dashboard — branding.** Settings → Branding. Upload FuelAlert logo, set primary colour to match app accent.
+- [ ] **Stripe Dashboard — Customer Portal.** Settings → Billing → Customer Portal. Allow plan changes across all 6 prices (basic/plus/pro × monthly/annual), cancellation at period end only, card updates, invoice history. Hide everything else.
+- [ ] **Stripe Dashboard — webhook endpoint.** Developers → Webhooks. Add endpoint at `{APP_URL}/stripe/webhook`. Subscribe to: `customer.subscription.created`, `customer.subscription.updated`, `customer.subscription.deleted`, `invoice.payment_succeeded`, `invoice.payment_failed`. Copy signing secret → production `.env` as `STRIPE_WEBHOOK_SECRET`.
+
+### Production env + server
+
+- [ ] **`.env` keys** set on production:
+ - `STRIPE_KEY=pk_live_...`
+ - `STRIPE_SECRET=sk_live_...`
+ - `STRIPE_WEBHOOK_SECRET=whsec_...`
+ - `CASHIER_CURRENCY=gbp`
+ - `QUEUE_CONNECTION=redis`
+ - `STRIPE_PRICE_BASIC_MONTHLY`, `STRIPE_PRICE_BASIC_ANNUAL`
+ - `STRIPE_PRICE_PLUS_MONTHLY`, `STRIPE_PRICE_PLUS_ANNUAL`
+ - `STRIPE_PRICE_PRO_MONTHLY`, `STRIPE_PRICE_PRO_ANNUAL`
+- [ ] **Run `php artisan migrate`** — adds `users.grace_period_until`.
+- [ ] **Queue worker** consuming both queues: `--queue=notifications,default` (reminders go on the `notifications` queue).
+- [ ] **Redis persistence** (AOF or RDB) enabled — delayed jobs sit for 3–5 days.
+- [ ] `php artisan route:list --name=billing` — confirm 4 routes (checkout, portal, success, cancel).
+
+### E2E QA (Stripe test mode)
+
+Requires the Dashboard + env tasks above done first. Stripe test cards:
+
+- `4242 4242 4242 4242` — success
+- `4000 0000 0000 0341` — renewal fails (use to test dunning)
+
+- [ ] Sign up on each paid tier × both cadences (6 combos) → confirm tier shows.
+- [ ] Upgrade basic → pro via Portal → confirm instant swap.
+- [ ] Downgrade pro → basic via Portal → confirm change scheduled for period end.
+- [ ] Cancel mid-period → features persist until period end → drop to free.
+- [ ] Use `4000 0000 0000 0341` + `stripe trigger invoice.payment_failed`:
+ - Banner appears on dashboard with correct "by {date}" string.
+ - Day-3 job is queued (visible via `php artisan queue:listen notifications`).
+ - Day-5 job is queued.
+ - Final Stripe retry fails → `customer.subscription.deleted` → user drops to free, WhatsApp + SMS prefs disabled, banner disappears.
+- [ ] Recover mid-grace (update card via Portal) → `invoice.payment_succeeded` clears grace, banner disappears, queued reminders silently no-op when they run.
+
+---