Payment System

Build your own Stripe integration

✗ Better to Buy

Let's be clear: you should not build a payment processor. Stripe, Paddle, and LemonSqueezy exist because payments involve banking regulations, PCI compliance, fraud detection, and currency handling that would consume your entire engineering team. But you absolutely can and should build sophisticated systems on top of these platforms.

Why This Works

Billing logic is yours

Usage-based pricing, custom invoicing, complex discounts—build exactly what your business needs.

Subscription management

Pause, upgrade, downgrade, prorate—your rules, not Stripe Billing's limitations.

Customer portal

Branded experience for managing subscriptions, not Stripe's generic portal.

Revenue recognition

Custom reporting, cohort analysis, MRR calculations your way.

Multi-gateway support

Abstract payment providers; switch from Stripe to Adyen without rewriting.

Dunning automation

Custom retry logic, personalized failed payment emails, win-back flows.

Tech Stack

LayerToolsWhy
Payment ProviderStripe or PaddleStripe for control, Paddle for simplicity (they handle tax). Use Stripe unless tax is painful.
Webhook HandlerInngest or customPayment webhooks must be idempotent and reliable. Inngest handles retries.
DatabasePostgreSQLFinancial data needs ACID guarantees. No eventual consistency for money.
State MachineXState or customSubscription states are complex. State machines prevent impossible transitions.
EmailResend + React EmailTransactional emails for invoices, receipts, failed payments.
PDF GenerationReact-pdf or PuppeteerBranded invoices and receipts. React-pdf for simple, Puppeteer for complex.

Architecture

┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ Checkout │────▶│ Your API │────▶│ Stripe │ │ (UI) │ │ (Billing) │ │ (Payments)│ └─────────────┘ └──────┬──────┘ └──────┬──────┘ │ │ ▼ │ ┌─────────────┐ │ │ PostgreSQL │◀────────────┘ │ (Subscriptions)│ (Webhooks) └─────────────┘

The Prompt

Copy this into Cursor, Claude, or ChatGPT to generate a working implementation:

Build a billing system on top of Stripe: ## Database Schema model Customer { id String @id @default(cuid()) userId String @unique user User @relation(fields: [userId], references: [id]) stripeCustomerId String @unique email String name String? subscriptions Subscription[] invoices Invoice[] paymentMethods PaymentMethod[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model Subscription { id String @id @default(cuid()) customerId String customer Customer @relation(fields: [customerId], references: [id]) stripeSubscriptionId String @unique stripePriceId String status SubscriptionStatus currentPeriodStart DateTime currentPeriodEnd DateTime cancelAtPeriodEnd Boolean @default(false) canceledAt DateTime? quantity Int @default(1) // Usage-based billing usageRecords UsageRecord[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } enum SubscriptionStatus { active past_due canceled unpaid trialing paused } model Invoice { id String @id @default(cuid()) customerId String customer Customer @relation(fields: [customerId], references: [id]) stripeInvoiceId String @unique amountDue Int amountPaid Int currency String status String invoicePdf String? hostedInvoiceUrl String? periodStart DateTime periodEnd DateTime createdAt DateTime @default(now()) } model UsageRecord { id String @id @default(cuid()) subscriptionId String subscription Subscription @relation(fields: [subscriptionId], references: [id]) quantity Int timestamp DateTime action String // 'increment' | 'set' idempotencyKey String @unique createdAt DateTime @default(now()) } ## Webhook Handlers Handle these Stripe events (must be idempotent): checkout.session.completed - Create/update customer record - Create subscription record - Provision access invoice.paid - Update subscription status - Record invoice - Send receipt email invoice.payment_failed - Update subscription to past_due - Trigger dunning email sequence - Schedule retry reminder customer.subscription.updated - Sync status, period, cancel flags - Handle plan changes - Adjust access/limits customer.subscription.deleted - Mark subscription canceled - Revoke access - Trigger win-back flow ## Customer Portal API GET /api/billing/subscription - Return current plan, status, usage, next billing date POST /api/billing/change-plan - Validate new plan is valid upgrade/downgrade - Calculate proration - Call Stripe to update subscription POST /api/billing/cancel - Set cancelAtPeriodEnd = true - Trigger feedback survey - Send confirmation email POST /api/billing/resume - Clear cancelAtPeriodEnd - Send confirmation POST /api/billing/update-payment-method - Create Stripe SetupIntent - Return client secret for frontend ## Usage-Based Billing POST /api/billing/usage - Validate subscription is active - Record usage with idempotency key - Report to Stripe (async, batched) GET /api/billing/usage - Return current period usage - Show projected invoice amount

Timeline

Week 1: Foundation

  • Set up Stripe account with test mode
  • Create webhook endpoint with signature verification
  • Implement checkout session creation
  • Handle checkout.completed webhook
  • Build customer and subscription records

Week 2: Subscription Management

  • Build customer portal pages
  • Implement plan change with proration preview
  • Add cancel/resume functionality
  • Handle failed payment webhooks
  • Create dunning email sequence

Week 3: Advanced Features

  • Add usage-based billing support
  • Build invoice history and PDF download
  • Implement multiple subscriptions per customer
  • Add team/seat-based billing option
  • Create admin dashboard with MRR metrics

Open Source References

Stripe Billing Examples ↗

Official examples for every integration pattern.

Tier ↗

Pricing engine abstraction. Interesting approach to plan management.

Lago ↗

Open-source billing platform. Study for complex metering.

Kill Bill ↗

Enterprise billing. Heavy but feature-complete.

Cost Comparison

Stripe Billing

0.5% + Stripe fees
On top of 2.9% + 30¢. Adds up at volume.

Self-Hosted

Stripe fees only
Build your own billing logic, skip Stripe Billing product.
0.5% of revenue
potential annual savings

Watch Out For