Stripe Setup
Subscriptions, webhooks, and billing portal configuration
ShipAddons uses Stripe for subscription management. This tutorial covers the complete setup process.
Subscription Model
The boilerplate supports:
- Multiple pricing tiers (Basic, Essential, Pro)
- Monthly recurring subscriptions
- Upgrade and downgrade via billing portal
- Webhook-driven status updates
1. Create Stripe Account
- Go to stripe.com and create an account
- For development, use Test Mode (toggle in the dashboard)
- [Optional] Complete account verification for production use
2. Get API Keys
From Developers > API keys, copy to your web/.env.local:
STRIPE_SECRET_KEY=sk_test_xxx3. Configure Products
Products are defined in web/constants.ts:
export const plans: Record<Plan["id"], Plan> = {
BASIC: {
id: "BASIC",
name: "Basic",
description: "Basic plan",
imageUrls: ["https://dummyimage.com/96x96/9/fff.png&text=Logo"],
priceInCents: 0,
mode: "subscription",
features: ["basic_feature"],
},
ESSENTIAL: {
id: "ESSENTIAL",
name: "Essential",
description: "Essential plan",
imageUrls: ["https://dummyimage.com/96x96/9/fff.png&text=Logo"],
priceInCents: 1000,
mode: "subscription",
features: ["essential_feature"],
stripePriceId: "price_1SedRfRgqD2Y1UgSc0mp1Vcb",
},
PRO: {
id: "PRO",
name: "Pro",
description: "Pro plan",
imageUrls: ["https://dummyimage.com/96x96/9/fff.png&text=Logo"],
priceInCents: 2000,
mode: "subscription",
features: ["pro_feature"],
stripePriceId: "price_1SedRgRgqD2Y1UgSfiv60YIY",
},
};4. Sync Products to Stripe
The stripe-sync.ts script creates products and prices in Stripe:
cd web
npx tsx scripts/stripe-sync.ts syncThis command:
- Creates products in Stripe matching your
constants.ts - Creates prices for each product
- Updates
constants.tswithstripePriceIdvalues
Run sync whenever you modify packages.
5. Set Up Webhook
Webhooks sync subscription state from Stripe to your database.
For Production
- Go to Developers > Webhooks
- Add endpoint:
https://your-domain.com/api/stripe/webhook - Select events:
checkout.session.completedcustomer.subscription.updatedcustomer.subscription.deleted
- Copy the signing secret to your
.env.local:
STRIPE_WEBHOOK_SECRET=whsec_xxxFor Local Development
Use the Stripe CLI to forward webhooks:
stripe listen --forward-to localhost:3001/api/stripe/webhookThe CLI prints a webhook secret to use locally.
Webhook Handling
The webhook handler at /api/stripe/webhook processes three events:
checkout.session.completed
Triggered when a customer completes checkout:
async function handleCheckoutSessionCompleted(session: Stripe.Checkout.Session) {
// Get user ID from session metadata
const userId = session.client_reference_id;
// Get subscription details
const subscription = await stripe.subscriptions.retrieve(subscriptionId);
// Upsert subscription in database
await supabaseAdmin
.from("subscriptions")
.upsert({
user_id: userId,
stripe_customer_id: customerId,
stripe_subscription_id: subscriptionId,
status: subscription.status,
plan_id: planId,
});
}customer.subscription.updated
Triggered when subscription changes (upgrade, downgrade, renewal):
async function handleSubscriptionUpdated(subscription: Stripe.Subscription) {
await supabaseAdmin
.from("subscriptions")
.update({
status: subscription.status,
current_period_end: new Date(subscription.current_period_end * 1000),
})
.eq("stripe_subscription_id", subscription.id);
}customer.subscription.deleted
Triggered when subscription is canceled:
async function handleSubscriptionDeleted(subscription: Stripe.Subscription) {
await supabaseAdmin
.from("subscriptions")
.update({ status: "canceled" })
.eq("stripe_subscription_id", subscription.id);
}6. Configure Billing Portal
The billing portal allows customers to manage their subscription:
npx tsx scripts/stripe-sync.ts portalThis configures the portal to:
- Show all subscription products
- Allow plan switching (upgrades/downgrades)
- Enable subscription cancellation
Copy the configuration ID to your .env.local:
STRIPE_PORTAL_CONFIG_ID=bpc_xxxCheckout Flow
The add-on initiates checkout via the /api/stripe/checkout endpoint:
// Client-side (in add-on)
const response = await fetch(`${API_BASE_URL}/api/stripe/checkout`, {
method: "POST",
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ packageId: "PRO" }),
});
const { data: checkoutUrl } = await response.json();
window.open(checkoutUrl, "_blank");The backend:
- Verifies the JWT
- Creates a Stripe checkout session
- Returns the checkout URL
- User completes payment on Stripe-hosted page
- Webhook updates subscription in database
Billing Portal
Customers access the billing portal to manage subscriptions:
// Client initiates portal session
const response = await fetch(`${API_BASE_URL}/api/stripe/billing_portal`, {
method: "POST",
headers: { "Authorization": `Bearer ${accessToken}` },
});
const { data: portalUrl } = await response.json();
window.open(portalUrl, "_blank");The portal allows:
- Viewing current subscription
- Switching plans (prorated automatically)
- Updating payment method
- Canceling subscription
Script Commands
| Command | Description |
|---|---|
npx tsx scripts/stripe-sync.ts sync | Create/update products and prices in Stripe |
npx tsx scripts/stripe-sync.ts portal | Configure billing portal |
npx tsx scripts/stripe-sync.ts list | List all Stripe products |
npx tsx scripts/stripe-sync.ts status | Show sync status |
Testing
- Use Stripe test cards:
- Success:
4242 4242 4242 4242 - Decline:
4000 0000 0000 0002
- Success:
- Check webhook delivery in Developers > Webhooks > Recent events
- Verify subscription in Supabase: Table Editor > subscriptions