* chore: bump version to 3.1.0 and update dependencies in package.json, pnpm-lock.yaml, and pnpm-workspace.yaml; enhance billing services with support for hosted checkout page in Stripe integration * Enhance error handling in billing services to log error messages instead of objects; update documentation for Stripe integration to clarify publishable key requirements based on UI mode.
309 lines
9.3 KiB
Plaintext
309 lines
9.3 KiB
Plaintext
---
|
|
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 |
|
|
| `STRIPE_UI_MODE` | Checkout UI mode: `embedded_page` (default) or `hosted_page` (optional) | - |
|
|
|
|
{% 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 %}
|
|
|
|
## Checkout UI Mode
|
|
|
|
Stripe supports two checkout UI modes:
|
|
|
|
- **`embedded_page`** (default): Embeds the checkout form directly in your application as a dialog popup. Requires `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY`.
|
|
- **`hosted_page`**: Redirects users to a Stripe-hosted checkout page. The publishable key is not required in this mode.
|
|
|
|
Configure this with the `STRIPE_UI_MODE` environment variable:
|
|
|
|
```bash
|
|
STRIPE_UI_MODE=hosted_page
|
|
```
|
|
|
|
If not set, it defaults to `embedded_page`.
|
|
|
|
## 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
|