Payment Integration Setup

Type: Execution Recipe Confidence: 0.92 Sources: 7 Verified: 2026-03-12

Purpose

This recipe integrates a complete payment system into an MVP — covering Stripe (subscriptions, one-time, usage-based), LemonSqueezy (merchant of record), and PayPal, with webhook handling, customer portal, and subscription lifecycle management. [src1]

Prerequisites

Constraints

Tool Selection Decision

Which payment approach?
├── SaaS with subscriptions AND want maximum control
│   └── PROVIDER A: Stripe Checkout + Billing
├── SaaS AND want zero tax/compliance burden
│   └── PROVIDER B: LemonSqueezy (merchant of record)
├── AI/API product with usage-based pricing
│   └── PROVIDER C: Stripe Meters + Billing
└── Marketplace with split payments
    └── PROVIDER D: Stripe Connect
ProviderFeeTax HandlingSetup TimePayout SpeedBest For
A: Stripe2.9% + $0.30Self-managed45-60 min2 daysSaaS, full control
B: LemonSqueezy5% + $0.50Included (MoR)30 minNet 15Solo founders, global
C: Stripe Meters2.9% + $0.30Self-managed60-90 min2 daysAI/API, metered
D: Stripe Connect2.9% + $0.30 + feePer-account2-4 hoursVariesMarketplaces

Execution Flow

Step 1: Configure Stripe and Create Products

Duration: 10 minutes · Tool: Stripe Dashboard + CLI

Create products and prices via Stripe CLI or Dashboard. Add API keys and price IDs to .env.local. Verify products visible in Stripe Dashboard. [src1]

Verify: Products and prices visible in Stripe Product Catalog. · If failed: Check API key permissions; ensure test mode is active.

Step 2: Create Checkout Session API Route

Duration: 10-15 minutes · Tool: Code editor

Create app/api/checkout/route.ts that creates a Stripe Checkout session with customer lookup/creation, subscription mode, and success/cancel URLs. For LemonSqueezy, use the Checkouts API. [src1] [src5]

Verify: "Subscribe" button redirects to checkout page; test card 4242 4242 4242 4242 works. · If failed: Check API keys and price/variant IDs.

Step 3: Add Payment Database Schema

Duration: 5-10 minutes · Tool: SQL Editor

Create customers table (links users to Stripe customer IDs) and subscriptions table (tracks status, price, period). Enable RLS. Add indexes for webhook lookups.

Verify: Tables created with RLS active. · If failed: Check auth.users reference exists.

Step 4: Implement Webhook Handler

Duration: 15-20 minutes · Tool: Code editor

Create app/api/webhooks/stripe/route.ts handling checkout.session.completed, subscription.created/updated/deleted, and invoice.payment_failed. Always verify webhook signatures with stripe.webhooks.constructEvent(). Use Stripe CLI for local forwarding: stripe listen --forward-to localhost:3000/api/webhooks/stripe. [src4] [src5]

Verify: Complete test checkout; subscription appears in database. · If failed: Check webhook signing secret; review Stripe Dashboard webhook logs.

Step 5: Add Customer Portal and Subscription Checks

Duration: 10 minutes · Tool: Code editor

Create portal session API route for self-service subscription management. Build server-side getUserSubscription() and requireSubscription() helpers. Enable Customer Portal in Stripe Dashboard.

Verify: Users can open portal to manage subscriptions; feature gates work. · If failed: Enable portal in Stripe Dashboard; verify customer ID is passed correctly.

Step 6: Configure Production Webhooks

Duration: 5 minutes · Tool: Stripe Dashboard

Add production webhook endpoint in Stripe Dashboard. Select events to listen for. Copy signing secret to production environment variables. Exclude webhook route from auth middleware.

Verify: Stripe shows successful deliveries. · If failed: Check webhook URL is public; update signing secret in production.

Output Schema

{
  "output_type": "payment_system",
  "format": "code + configured provider",
  "columns": [
    {"name": "payment_provider", "type": "string", "description": "Provider used", "required": true},
    {"name": "billing_model", "type": "string", "description": "Subscription, one-time, or usage-based", "required": true},
    {"name": "plans_configured", "type": "string", "description": "Plan names and price IDs", "required": true},
    {"name": "webhook_endpoint", "type": "string", "description": "Webhook URL", "required": true},
    {"name": "customer_portal", "type": "boolean", "description": "Self-service portal enabled", "required": true}
  ],
  "expected_row_count": "1",
  "sort_order": "N/A",
  "deduplication_key": "payment_provider"
}

Quality Benchmarks

Quality MetricMinimum AcceptableGoodExcellent
Checkout completionFlow works end-to-end+ Error handling+ Retry logic
Webhook reliabilityAll events processed+ Idempotent handlers+ Dead letter queue
Subscription syncStatus synced to DB+ Plan/price synced+ Usage metrics
Customer portalUsers can cancel+ Upgrade/downgrade+ Invoice history
SecuritySignature verified+ HTTPS + rate limiting+ Stripe Radar

If below minimum: Verify webhook signature checking. Test with Stripe test cards (success, decline, 3D Secure).

Error Handling

ErrorLikely CauseRecovery Action
No such price: price_xxxWrong price ID or test/live mismatchVerify price ID in Dashboard; check mode
Webhook signature failedWrong signing secret or parsed bodyUse raw body for verification; update secret
Customer not foundTest/live mode mismatchEnsure consistent mode; recreate customer
Subscription in Stripe but not in DBWebhook failed to processCheck Stripe webhook logs; replay events
Webhook endpoint unreachableAuth middleware blocking or firewallExclude webhook route from auth middleware

Cost Breakdown

ComponentStripeLemonSqueezyStripe + Tax
Transaction fee2.9% + $0.305% + $0.502.9% + $0.30
Tax complianceSelf-managedIncluded+ 0.5%
At $1,000 MRR~$32/mo~$55/mo~$37/mo
At $10,000 MRR~$320/mo~$550/mo~$370/mo
At $100,000 MRR~$3,200/mo~$5,500/mo~$3,700/mo

Anti-Patterns

Wrong: Processing webhooks without signature verification

Attackers can POST fake events to grant themselves premium access. [src4]

Correct: Always verify with stripe.webhooks.constructEvent()

Use raw request body and webhook signing secret. Return 400 for invalid signatures.

Wrong: Checking subscription status client-side only

Client state can be manipulated to appear subscribed. [src5]

Correct: Check server-side on every protected request

Query the subscriptions table (synced via webhooks) in server components and middleware.

Wrong: Storing card numbers in your database

Violates PCI-DSS and exposes massive liability. [src1]

Correct: Use Stripe Checkout or Elements exclusively

Stripe handles all card data in their PCI-compliant infrastructure.

When This Matters

Use this recipe when a developer needs to add payment processing to an MVP with authenticated users. Requires working authentication and a database. Covers checkout, subscriptions, webhooks, customer portal, and usage-based billing — not pricing strategy or revenue analytics.

Related Units