Why Stripe Churn Deflection?
1. High-level purpose
Stripe Churn Deflection is a focused micro-SaaS built to reduce churn for subscription-based products by automating dunning (failed payment retries and reminders), offering recovery workflows, and providing an admin interface for management and recovery operations.
2. What it does (Features)
- Stripe integration: webhook receiver, Checkout sessions, and subscription management.
- Dunning automation: create
DunningCaserecords, scheduleRetryAttemptjobs, send reminders, and record recovery attribution. - Admin UI & API: management pages, safe mode, audits, and manual retries.
- Auditing & logs: raw webhook capture and admin audit logs.
3. Supported workflows
Normal billing, retry/reminder flows, manual admin recovery, and export/audit workflows are supported.
4. Core data models (Prisma)
// User, Subscription, DunningCase, RetryAttempt, DunningReminder, RecoveryAttribution, Settings, StripeEventLog, AuditLog, CspReport
5. Technology stack
Next.js 14 + TypeScript, Prisma, SQLite (dev) / Postgres (prod), Stripe SDK, optional Redis, Postmark/SMTP, Sentry, Jest + Playwright.
6. Notable locations
pages/api/stripe/webhook.ts, pages/api/cron/*, pages/admin/*, lib/prisma.ts, scripts/seed-demo.js.
7. Simple yet effective dev workflow
npm install
npx prisma generate
npx prisma db push
node scripts/seed-demo.js
npm run dev
8. CI & E2E
CI runs unit tests (Jest) and Playwright E2E tests. Use the `DATABASE_URL`condition to switch migrations vs db push behavior.
9. Security
Validate webhooks, use secret managers, protect admin endpoints, and enable Sentry in production.
10. Observability
Sentry, StripeEventLog, and AuditLog are primary telemetry sources. Add Prometheus metrics for queue backlog and webhook failure rates.
11. Limitations
SQLite vs Postgres differences and considerations around enums and FK behavior noted.
. Next steps
- Enable Postgres in staging, run migrations.
- Enable Sentry and secrets manager in prod.
- Separate cron runner for scale.
Architecture diagram
14. Quick start
Run the dev workflow above and open http://localhost:3000.
Quick code snippets
Stripe webhook (Next.js API route, TypeScript)
import type { NextApiRequest, NextApiResponse } from 'next'
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: '2024-11-15' })
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const sig = req.headers['stripe-signature'] as string | undefined
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!
let event: Stripe.Event
try {
event = stripe.webhooks.constructEvent(req.body, sig!, webhookSecret)
} catch (err) {
console.error('Webhook signature verification failed', err)
return res.status(400).send('Invalid signature')
}
switch (event.type) {
case 'invoice.payment_failed':
// create or update DunningCase, schedule RetryAttempt
break
case 'invoice.payment_succeeded':
// mark recovered, create RecoveryAttribution
break
default:
break
}
res.status(200).json({ received: true })
}
Cron-like API stub (secure endpoint to run retries)
// pages/api/cron/run-dunning.ts
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(req: NextApiRequest, res: NextApiResponse){
const secret = req.query.secret || req.headers['x-cron-secret']
if(secret !== process.env.CRON_SECRET) return res.status(401).send('Unauthorized')
// find due RetryAttempt records, attempt charges using Stripe SDK, update statuses
// keep idempotency and backoff safety
return res.status(200).json({ ok: true })
}