Stripe Subscriptions
Stripe Subscriptions
Get paid. Warpkit handles checkout, billing portal, webhooks, and subscription gating.
Setup
1. Create products in Stripe dashboard
Create one product per plan. Copy the price IDs.
2. Set env vars
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
VITE_STRIPE_PRO_PRICE_ID=price_...
VITE_STRIPE_PRO_YEARLY_PRICE_ID=price_...
3. Configure plans in config.ts
stripe: {
plans: [
{
id: 'pro',
name: 'Pro',
popular: true,
description: 'Everything you need to run your SaaS.',
features: ['Unlimited items', 'Priority support', 'API access'],
recurring: {
priceId: import.meta.env.VITE_STRIPE_PRO_PRICE_ID ?? '',
price: 29,
yearlyPriceId: import.meta.env.VITE_STRIPE_PRO_YEARLY_PRICE_ID ?? '',
yearlyPrice: 290,
},
},
] as StripePlan[],
},
Checkout flow
The <CheckoutButton> component calls createCheckoutSession and redirects to Stripe:
import { CheckoutButton } from '@/components/landing';
<CheckoutButton priceId={plan.priceId} interval="monthly">
Get started
</CheckoutButton>
On success, Stripe redirects to /billing?success=true.
Billing portal
Users manage their subscription at /billing. The <BillingPage> calls createBillingPortal and redirects to Stripe's hosted portal for upgrades, downgrades, and cancellations.
Webhooks
The webhook handler lives at POST /api/v1/stripe-webhook. It processes:
checkout.session.completed: create subscription recordcustomer.subscription.updated: update plan / statuscustomer.subscription.deleted: mark subscription cancelledinvoice.payment_failed: mark subscription past_due
Stripe signature verification is built in. To test locally:
stripe listen --forward-to localhost:3000/api/v1/stripe-webhook
Gating features by subscription
In a server function:
import { db } from '@/lib/db';
import { subscriptions } from '@/lib/db/schema';
import { ERROR_CODES } from '@/lib/constants';
const sub = await db.query.subscriptions.findFirst({
where: eq(subscriptions.userId, user.id),
});
const isPro = sub?.status === 'active';
if (!isPro) return err(ERROR_CODES.NO_SUBSCRIPTION, 'Pro plan required');
In a component:
import { UpgradeGate } from '@/features/billing';
<UpgradeGate hasAccess={isPro}>
<ProFeature />
</UpgradeGate>