Billing
Stripe multi-plan billing with upgrade, downgrade, and cancellation flows.
TheShipStack includes full Stripe billing wired end-to-end — checkout, webhooks, plan enforcement, and in-app subscription management.
Plans
Three paid plans on top of a free tier:
| Plan | Monthly | Annual |
|---|---|---|
| Starter | configurable | configurable |
| Pro | configurable | configurable |
| Business | configurable | configurable |
Plan definitions (names, prices, limits) live in lib/plans.ts. Update that file to match your Stripe Price objects.
How it works
- Checkout — users click a plan on
/dashboard/pricing, a Stripe Checkout session is created, and they're redirected to Stripe to complete payment - Webhook sync — Stripe sends events to
/api/webhooks/stripe; the handler updates thesubscriptiontable with status, plan, and period dates - Plan enforcement — server actions read the subscription from the database and compare usage against plan limits before allowing operations
Subscription status
The subscription table stores:
status—active,trialing,past_due,canceled,inactiveplanId—starter,pro,business, ornullcancelAtPeriodEnd— whether cancellation is scheduledcurrentPeriodEnd— when the current period ends
Checking the subscription in your code
import { getOrgSubscription } from '@/lib/subscription'
const sub = await getOrgSubscription(orgId)
const isPro = sub?.status === 'active' || sub?.status === 'trialing'Enforcing plan limits
import { getPlanLimits } from '@/lib/plans'
import { getOrgSubscription } from '@/lib/subscription'
const sub = await getOrgSubscription(orgId)
const limits = getPlanLimits(sub?.status === 'active', sub?.planId)
if (limits.members !== null && currentMemberCount >= limits.members) {
throw new Error('UPGRADE_REQUIRED')
}Upgrade and downgrade
Users manage their plan from the workspace billing tab (/dashboard/workspace/settings?tab=billing). Upgrades and downgrades update the Stripe subscription immediately with proration_behavior: 'always_invoice' — Stripe generates an invoice for the difference right away.
Downgrades check current usage against the new plan's limits and block the action with a clear message if the workspace would exceed them.
Cancellation
Cancelling sets cancel_at_period_end: true on the Stripe subscription. Access continues until the end of the billing period. The billing settings page shows the access-until date.
Adding a new plan
- Create the product and price in the Stripe dashboard
- Add the plan to
lib/plans.tswith its name, price, limits, and Price ID env vars - Add the corresponding env vars to
.envand Vercel
Webhook events handled
| Event | Action |
|---|---|
customer.subscription.created | Activates subscription |
customer.subscription.updated | Syncs status, plan, and dates |
customer.subscription.deleted | Marks subscription as canceled |
invoice.payment_failed | Sets status to past_due, notifies owner |