Documentation

Billing & Payments

Accept payments with Stripe for subscriptions and one-time purchases.

Overview

ShipSecure includes a complete Stripe integration for handling payments:

  • Subscription billing (monthly/yearly)
  • Automatic renewals and upgrades
  • Customer portal for self-service management
  • Webhook handlers for real-time events

Quick Start

1. Set Up Stripe Account

  1. Go to Stripe Dashboard and create an account
  2. Get your API keys from Dashboard → API Keys

2. Configure Environment Variables

Add these to your .env.local file:

# Stripe API Keys
STRIPE_SECRET_KEY="sk_test_..."
STRIPE_WEBHOOK_SECRET="whsec_..."

# Price IDs (from Stripe Dashboard)
STRIPE_PRICE_PRO_MONTHLY="price_..."
STRIPE_PRICE_PRO_YEARLY="price_..."

3. Create Products in Stripe

  1. Go to Products
  2. Click "Add Product"
  3. Add pricing:
    • Monthly: $29/month (example)
    • Yearly: $290/year (example)
  4. Copy the Price IDs to your .env.local

4. Set Up Webhooks

  1. Go to Developers → Webhooks
  2. Click "Add endpoint"
  3. Enter your webhook URL:
    • Development: Use Stripe CLI or ngrok
    • Production: https://yourdomain.com/api/webhooks/stripe
  4. Select these events:
    • checkout.session.completed
    • customer.subscription.created
    • customer.subscription.updated
    • customer.subscription.deleted
    • invoice.payment_succeeded
    • invoice.payment_failed

How It Works

User clicks "Subscribe" → Stripe Checkout → Payment → Webhook → Database Update

Flow:

  1. User clicks upgrade button
  2. API creates Stripe Checkout Session
  3. User completes payment on Stripe
  4. Stripe sends webhook to your server
  5. Webhook handler updates user's plan in database
  6. User now has PRO access

API Routes

Create Checkout Session

// POST /api/billing/checkout
const response = await fetch("/api/billing/checkout", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    interval: "monthly", // or "yearly"
  }),
});

const { url } = await response.json();
window.location.href = url; // Redirect to Stripe

Open Customer Portal

// POST /api/billing/portal
const response = await fetch("/api/billing/portal", {
  method: "POST",
});

const { url } = await response.json();
window.location.href = url; // Redirect to Stripe Portal

Database Schema

The User model includes subscription fields:

model User {
  // ... other fields
  plan          Plan      @default(FREE)
  customerId    String?   // Stripe Customer ID
  subscriptionId String?  // Active Stripe Subscription ID
}

enum Plan {
  FREE
  PRO
}

Webhook Events

The webhook handler processes these Stripe events:

  • checkout.session.completed — Upgrade user to PRO plan
  • customer.subscription.updated — Sync plan status with Stripe
  • customer.subscription.deleted — Downgrade user to FREE plan
  • invoice.payment_failed — Send notification (optional)

Testing

Local Development

Use Stripe CLI to forward webhooks:

# Install Stripe CLI
brew install stripe/stripe-cli/stripe

# Login to Stripe
stripe login

# Forward webhooks to local server
stripe listen --forward-to localhost:3000/api/webhooks/stripe

Test Cards

Use these test card numbers for development:

  • 4242 4242 4242 4242 — Successful payment
  • 4000 0000 0000 0002 — Card declined
  • 4000 0025 0000 3155 — Requires authentication (3D Secure)

Customization

Add More Plans

  1. Update prisma/schema.prisma:
enum Plan {
  FREE
  PRO
  ENTERPRISE
}
  1. Update src/lib/stripe.ts:
export const PLANS = {
  FREE: { ... },
  PRO: { ... },
  ENTERPRISE: {
    name: "Enterprise",
    priceMonthly: 49,
    features: ["All Pro features", "Dedicated support", "SLA"],
  },
};
  1. Run npx prisma db push to update database

One-Time Payment

Change checkout mode from subscription to payment:

const session = await stripe.checkout.sessions.create({
  mode: "payment", // Instead of "subscription"
  // ...
});

Security

  • Webhook signature verification — All webhooks are verified
  • No card data stored — Card data never touches your server
  • PCI compliant — Stripe handles all compliance

Troubleshooting

Webhook not working

Check these:

  1. Webhook URL is correct in Stripe Dashboard
  2. STRIPE_WEBHOOK_SECRET is set in .env.local
  3. Server logs show incoming webhook requests
  4. Stripe Dashboard shows successful webhook deliveries

Customer not upgraded

Check these:

  1. Webhook events appear in Stripe Dashboard
  2. metadata.userId is set in checkout session
  3. User exists in database with matching ID
  4. Server logs show subscription update

Payment declined

Check these:

  1. Using test cards in test mode
  2. Card has sufficient funds
  3. No fraud prevention blocks

Next Steps