Next.js Supabase V3 (#463)
Version 3 of the kit: - Radix UI replaced with Base UI (using the Shadcn UI patterns) - next-intl replaces react-i18next - enhanceAction deprecated; usage moved to next-safe-action - main layout now wrapped with [locale] path segment - Teams only mode - Layout updates - Zod v4 - Next.js 16.2 - Typescript 6 - All other dependencies updated - Removed deprecated Edge CSRF - Dynamic Github Action runner
This commit is contained in:
committed by
GitHub
parent
4912e402a3
commit
7ebff31475
292
docs/billing/stripe.mdoc
Normal file
292
docs/billing/stripe.mdoc
Normal file
@@ -0,0 +1,292 @@
|
||||
---
|
||||
status: "published"
|
||||
label: "Stripe"
|
||||
title: "Configure Stripe Billing for Your Next.js SaaS"
|
||||
description: "Complete guide to setting up Stripe payments in Makerkit. Configure subscriptions, one-off payments, webhooks, and the Customer Portal for your Next.js Supabase application."
|
||||
order: 2
|
||||
---
|
||||
|
||||
Stripe is the default billing provider in Makerkit. It offers the most flexibility with support for multiple line items, metered billing, and advanced subscription management.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you start:
|
||||
1. Create a [Stripe account](https://dashboard.stripe.com/register)
|
||||
2. Have your Stripe API keys ready (Dashboard → Developers → API keys)
|
||||
3. Install the Stripe CLI for local webhook testing
|
||||
|
||||
## Step 1: Environment Variables
|
||||
|
||||
Add these variables to your `.env.local` file:
|
||||
|
||||
```bash
|
||||
# Stripe API Keys
|
||||
STRIPE_SECRET_KEY=sk_test_...
|
||||
STRIPE_WEBHOOK_SECRET=whsec_...
|
||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
|
||||
```
|
||||
|
||||
| Variable | Description | Where to Find |
|
||||
|----------|-------------|---------------|
|
||||
| `STRIPE_SECRET_KEY` | Server-side API key | Dashboard → Developers → API keys |
|
||||
| `STRIPE_WEBHOOK_SECRET` | Webhook signature verification | Generated by Stripe CLI or Dashboard |
|
||||
| `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` | Client-side key (safe to expose) | Dashboard → Developers → API keys |
|
||||
|
||||
{% alert type="error" title="Never commit secret keys" %}
|
||||
Add `STRIPE_SECRET_KEY` and `STRIPE_WEBHOOK_SECRET` to `.env.local` only. Never add them to `.env` or commit them to your repository.
|
||||
{% /alert %}
|
||||
|
||||
## Step 2: Configure Billing Provider
|
||||
|
||||
Ensure Stripe is set as your billing provider:
|
||||
|
||||
```bash
|
||||
NEXT_PUBLIC_BILLING_PROVIDER=stripe
|
||||
```
|
||||
|
||||
And in the database:
|
||||
|
||||
```sql
|
||||
UPDATE public.config SET billing_provider = 'stripe';
|
||||
```
|
||||
|
||||
## Step 3: Create Products in Stripe
|
||||
|
||||
1. Go to Stripe Dashboard → Products
|
||||
2. Click **Add product**
|
||||
3. Configure your product:
|
||||
- **Name**: "Pro Plan", "Starter Plan", etc.
|
||||
- **Pricing**: Add prices for monthly and yearly intervals
|
||||
- **Price ID**: Copy the `price_xxx` ID for your billing schema
|
||||
|
||||
**Important:** The Price ID (e.g., `price_1NNwYHI1i3VnbZTqI2UzaHIe`) must match the `id` field in your billing schema's line items.
|
||||
|
||||
## Step 4: Set Up Local Webhooks with Stripe CLI
|
||||
|
||||
The Stripe CLI forwards webhook events from Stripe to your local development server.
|
||||
|
||||
### Using Docker (Recommended)
|
||||
|
||||
First, log in to Stripe:
|
||||
|
||||
```bash
|
||||
docker run --rm -it --name=stripe \
|
||||
-v ~/.config/stripe:/root/.config/stripe \
|
||||
stripe/stripe-cli:latest login
|
||||
```
|
||||
|
||||
This opens a browser window to authenticate. Complete the login process.
|
||||
|
||||
Then start listening for webhooks:
|
||||
|
||||
```bash
|
||||
pnpm run stripe:listen
|
||||
```
|
||||
|
||||
Or manually:
|
||||
|
||||
```bash
|
||||
docker run --rm -it --name=stripe \
|
||||
-v ~/.config/stripe:/root/.config/stripe \
|
||||
stripe/stripe-cli:latest listen \
|
||||
--forward-to http://host.docker.internal:3000/api/billing/webhook
|
||||
```
|
||||
|
||||
### Using Stripe CLI Directly
|
||||
|
||||
If you prefer installing Stripe CLI globally:
|
||||
|
||||
```bash
|
||||
# macOS
|
||||
brew install stripe/stripe-cli/stripe
|
||||
|
||||
# Login
|
||||
stripe login
|
||||
|
||||
# Listen for webhooks
|
||||
stripe listen --forward-to localhost:3000/api/billing/webhook
|
||||
```
|
||||
|
||||
### Copy the Webhook Secret
|
||||
|
||||
When you start listening, the CLI displays a webhook signing secret:
|
||||
|
||||
```
|
||||
> Ready! Your webhook signing secret is whsec_xxxxxxxxxxxxx
|
||||
```
|
||||
|
||||
Copy this value and add it to your `.env.local`:
|
||||
|
||||
```bash
|
||||
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxx
|
||||
```
|
||||
|
||||
{% alert type="default" title="Re-run after restart" %}
|
||||
The webhook secret changes each time you restart the Stripe CLI. Update your `.env.local` accordingly.
|
||||
{% /alert %}
|
||||
|
||||
### Linux Troubleshooting
|
||||
|
||||
If webhooks aren't reaching your app on Linux, try adding `--network=host`:
|
||||
|
||||
```bash
|
||||
docker run --rm -it --name=stripe \
|
||||
-v ~/.config/stripe:/root/.config/stripe \
|
||||
stripe/stripe-cli:latest listen \
|
||||
--network=host \
|
||||
--forward-to http://localhost:3000/api/billing/webhook
|
||||
```
|
||||
|
||||
## Step 5: Configure Customer Portal
|
||||
|
||||
The Stripe Customer Portal lets users manage their subscriptions, payment methods, and invoices.
|
||||
|
||||
1. Go to Stripe Dashboard → Settings → Billing → Customer portal
|
||||
2. Configure these settings:
|
||||
|
||||
**Payment methods:**
|
||||
- Allow customers to update payment methods: ✅
|
||||
|
||||
**Subscriptions:**
|
||||
- Allow customers to switch plans: ✅
|
||||
- Choose products customers can switch between
|
||||
- Configure proration behavior
|
||||
|
||||
**Cancellations:**
|
||||
- Allow customers to cancel subscriptions: ✅
|
||||
- Configure cancellation behavior (immediate vs. end of period)
|
||||
|
||||
**Invoices:**
|
||||
- Allow customers to view invoice history: ✅
|
||||
|
||||
{% img src="/assets/images/docs/stripe-customer-portal.webp" width="2712" height="1870" /%}
|
||||
|
||||
## Step 6: Production Webhooks
|
||||
|
||||
When deploying to production, configure webhooks in the Stripe Dashboard:
|
||||
|
||||
1. Go to Stripe Dashboard → Developers → Webhooks
|
||||
2. Click **Add endpoint**
|
||||
3. Enter your webhook URL: `https://yourdomain.com/api/billing/webhook`
|
||||
4. Select events to listen for:
|
||||
|
||||
**Required events:**
|
||||
- `checkout.session.completed`
|
||||
- `customer.subscription.created`
|
||||
- `customer.subscription.updated`
|
||||
- `customer.subscription.deleted`
|
||||
|
||||
**For one-off payments (optional):**
|
||||
- `checkout.session.async_payment_failed`
|
||||
- `checkout.session.async_payment_succeeded`
|
||||
|
||||
5. Click **Add endpoint**
|
||||
6. Copy the signing secret and add it to your production environment variables
|
||||
|
||||
{% alert type="warning" title="Use a public URL" %}
|
||||
Webhook URLs must be publicly accessible. Vercel preview deployments with authentication enabled won't work. Test by visiting the URL in an incognito browser window.
|
||||
{% /alert %}
|
||||
|
||||
## Free Trials Without Credit Card
|
||||
|
||||
Allow users to start a trial without entering payment information:
|
||||
|
||||
```bash
|
||||
STRIPE_ENABLE_TRIAL_WITHOUT_CC=true
|
||||
```
|
||||
|
||||
When enabled, users can start a subscription with a trial period and won't be charged until the trial ends. They'll need to add a payment method before the trial expires.
|
||||
|
||||
You must also set `trialDays` in your billing schema:
|
||||
|
||||
```tsx
|
||||
{
|
||||
id: 'pro-monthly',
|
||||
name: 'Pro Monthly',
|
||||
paymentType: 'recurring',
|
||||
interval: 'month',
|
||||
trialDays: 14, // 14-day free trial
|
||||
lineItems: [/* ... */],
|
||||
}
|
||||
```
|
||||
|
||||
## Migrating Existing Subscriptions
|
||||
|
||||
If you're migrating to Makerkit with existing Stripe subscriptions, you need to add metadata to each subscription.
|
||||
|
||||
Makerkit expects this metadata on subscriptions:
|
||||
|
||||
```json
|
||||
{
|
||||
"accountId": "uuid-of-the-account"
|
||||
}
|
||||
```
|
||||
|
||||
**Option 1: Add metadata manually**
|
||||
|
||||
Use the Stripe Dashboard or a migration script to add the `accountId` metadata to existing subscriptions.
|
||||
|
||||
**Option 2: Modify the webhook handler**
|
||||
|
||||
If you can't update metadata, modify the webhook handler to look up accounts by customer ID:
|
||||
|
||||
```tsx {% title="packages/billing/stripe/src/services/stripe-webhook-handler.service.ts" %}
|
||||
// Instead of:
|
||||
const accountId = subscription.metadata.accountId as string;
|
||||
|
||||
// Query your database:
|
||||
const { data: customer } = await supabase
|
||||
.from('billing_customers')
|
||||
.select('account_id')
|
||||
.eq('customer_id', subscription.customer)
|
||||
.single();
|
||||
|
||||
const accountId = customer?.account_id;
|
||||
```
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Webhooks not received
|
||||
|
||||
1. **Check the CLI is running:** `pnpm run stripe:listen` should show "Ready!"
|
||||
2. **Verify the secret:** Copy the new webhook secret after each CLI restart
|
||||
3. **Check the account:** Ensure you're logged into the correct Stripe account
|
||||
4. **Check the URL:** The webhook endpoint is `/api/billing/webhook`
|
||||
|
||||
### "No such price" error
|
||||
|
||||
The Price ID in your billing schema doesn't exist in Stripe. Verify:
|
||||
1. You're using test mode keys with test mode prices (or live with live)
|
||||
2. The Price ID is copied correctly from Stripe Dashboard
|
||||
|
||||
### Subscription not appearing in database
|
||||
|
||||
1. Check webhook logs in Stripe Dashboard → Developers → Webhooks
|
||||
2. Look for errors in your application logs
|
||||
3. Verify the `accountId` is correctly passed in checkout metadata
|
||||
|
||||
### Customer Portal not loading
|
||||
|
||||
1. Ensure the Customer Portal is configured in Stripe Dashboard
|
||||
2. Check that the customer has a valid subscription
|
||||
3. Verify the `customerId` is correct
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
Before going live:
|
||||
|
||||
- [ ] Test subscription checkout with test card `4242 4242 4242 4242`
|
||||
- [ ] Verify subscription appears in user's billing section
|
||||
- [ ] Test subscription upgrade/downgrade via Customer Portal
|
||||
- [ ] Test subscription cancellation
|
||||
- [ ] Verify webhook events are processed correctly
|
||||
- [ ] Test with failing card `4000 0000 0000 0002` to verify error handling
|
||||
- [ ] For trials: test trial expiration and conversion to paid
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Billing Overview](/docs/next-supabase-turbo/billing/overview) - Architecture and concepts
|
||||
- [Billing Schema](/docs/next-supabase-turbo/billing/billing-schema) - Configure your pricing
|
||||
- [Webhooks](/docs/next-supabase-turbo/billing/billing-webhooks) - Custom webhook handling
|
||||
- [Metered Usage](/docs/next-supabase-turbo/billing/metered-usage) - Report usage to Stripe
|
||||
- [Per-Seat Billing](/docs/next-supabase-turbo/billing/per-seat-billing) - Team-based pricing
|
||||
Reference in New Issue
Block a user