Files
myeasycms-v2/docs/billing/stripe.mdoc
Giancarlo Buomprisco 6268d1bab0 Updated dependencies, Added Hosted mode for Stripe checkout
* 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.
2026-03-31 12:44:30 +08:00

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