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
- Go to Stripe Dashboard and create an account
- 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
- Go to Products
- Click "Add Product"
- Add pricing:
- Monthly: $29/month (example)
- Yearly: $290/year (example)
- Copy the Price IDs to your
.env.local
4. Set Up Webhooks
- Go to Developers → Webhooks
- Click "Add endpoint"
- Enter your webhook URL:
- Development: Use Stripe CLI or ngrok
- Production:
https://yourdomain.com/api/webhooks/stripe
- Select these events:
checkout.session.completedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.payment_succeededinvoice.payment_failed
How It Works
User clicks "Subscribe" → Stripe Checkout → Payment → Webhook → Database Update
Flow:
- User clicks upgrade button
- API creates Stripe Checkout Session
- User completes payment on Stripe
- Stripe sends webhook to your server
- Webhook handler updates user's plan in database
- 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
- Update
prisma/schema.prisma:
enum Plan {
FREE
PRO
ENTERPRISE
}
- Update
src/lib/stripe.ts:
export const PLANS = {
FREE: { ... },
PRO: { ... },
ENTERPRISE: {
name: "Enterprise",
priceMonthly: 49,
features: ["All Pro features", "Dedicated support", "SLA"],
},
};
- Run
npx prisma db pushto 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:
- Webhook URL is correct in Stripe Dashboard
STRIPE_WEBHOOK_SECRETis set in.env.local- Server logs show incoming webhook requests
- Stripe Dashboard shows successful webhook deliveries
Customer not upgraded
Check these:
- Webhook events appear in Stripe Dashboard
metadata.userIdis set in checkout session- User exists in database with matching ID
- Server logs show subscription update
Payment declined
Check these:
- Using test cards in test mode
- Card has sufficient funds
- No fraud prevention blocks
Next Steps
- Customization — Customize pricing page
- Security — Secure your payment flow
- Deployment — Go live with production Stripe keys
- FAQ — Common questions answered