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
267 lines
7.6 KiB
Plaintext
267 lines
7.6 KiB
Plaintext
---
|
|
status: "published"
|
|
label: "Lemon Squeezy"
|
|
title: "Configure Lemon Squeezy Billing for Your Next.js SaaS"
|
|
order: 3
|
|
description: "Complete guide to setting up Lemon Squeezy payments in Makerkit. Lemon Squeezy is a Merchant of Record that handles global tax compliance, billing, and payments for your SaaS."
|
|
---
|
|
|
|
Lemon Squeezy is a Merchant of Record (MoR), meaning they handle all billing complexity for you: VAT, sales tax, invoicing, and compliance across 100+ countries. You receive payouts minus their fees.
|
|
|
|
## Why Choose Lemon Squeezy?
|
|
|
|
**Pros:**
|
|
- Automatic global tax compliance (VAT, GST, sales tax)
|
|
- No need to register for tax collection in different countries
|
|
- Simpler setup than Stripe for international sales
|
|
- Built-in license key generation (great for desktop apps)
|
|
- Lower complexity for solo founders
|
|
|
|
**Cons:**
|
|
- One line item per plan (no mixing flat + metered + per-seat)
|
|
- Less flexibility than Stripe
|
|
- Higher fees than Stripe in some regions
|
|
|
|
## Prerequisites
|
|
|
|
1. Create a [Lemon Squeezy account](https://lemonsqueezy.com)
|
|
2. Create a Store in your Lemon Squeezy dashboard
|
|
3. Create Products and Variants for your pricing plans
|
|
4. Set up a webhook endpoint
|
|
|
|
## Step 1: Environment Variables
|
|
|
|
Add these variables to your `.env.local`:
|
|
|
|
```bash
|
|
LEMON_SQUEEZY_SECRET_KEY=your_api_key_here
|
|
LEMON_SQUEEZY_SIGNING_SECRET=your_webhook_signing_secret
|
|
LEMON_SQUEEZY_STORE_ID=your_store_id
|
|
```
|
|
|
|
| Variable | Description | Where to Find |
|
|
|----------|-------------|---------------|
|
|
| `LEMON_SQUEEZY_SECRET_KEY` | API key for server-side calls | Settings → API |
|
|
| `LEMON_SQUEEZY_SIGNING_SECRET` | Webhook signature verification | Settings → Webhooks |
|
|
| `LEMON_SQUEEZY_STORE_ID` | Your store's numeric ID | Settings → Stores |
|
|
|
|
{% alert type="error" title="Keep secrets secure" %}
|
|
Add these to `.env.local` only. Never commit them to your repository or add them to `.env`.
|
|
{% /alert %}
|
|
|
|
## Step 2: Configure Billing Provider
|
|
|
|
Set Lemon Squeezy as your billing provider in the environment:
|
|
|
|
```bash
|
|
NEXT_PUBLIC_BILLING_PROVIDER=lemon-squeezy
|
|
```
|
|
|
|
And update the database:
|
|
|
|
```sql
|
|
UPDATE public.config SET billing_provider = 'lemon-squeezy';
|
|
```
|
|
|
|
## Step 3: Create Products in Lemon Squeezy
|
|
|
|
1. Go to your Lemon Squeezy Dashboard → Products
|
|
2. Click **New Product**
|
|
3. Configure your product:
|
|
- **Name**: "Pro Plan", "Starter Plan", etc.
|
|
- **Pricing**: Choose subscription or one-time
|
|
- **Variant**: Create variants for different billing intervals
|
|
|
|
4. Copy the **Variant ID** (not Product ID) for your billing schema
|
|
|
|
The Variant ID looks like `123456` (numeric). This goes in your line item's `id` field.
|
|
|
|
## Step 4: Update Billing Schema
|
|
|
|
Lemon Squeezy has a key limitation: **one line item per plan**. You cannot mix flat, per-seat, and metered billing in a single plan.
|
|
|
|
```tsx
|
|
import { createBillingSchema } from '@kit/billing';
|
|
|
|
export default createBillingSchema({
|
|
provider: 'lemon-squeezy',
|
|
products: [
|
|
{
|
|
id: 'pro',
|
|
name: 'Pro',
|
|
description: 'For professionals',
|
|
currency: 'USD',
|
|
plans: [
|
|
{
|
|
id: 'pro-monthly',
|
|
name: 'Pro Monthly',
|
|
paymentType: 'recurring',
|
|
interval: 'month',
|
|
lineItems: [
|
|
{
|
|
id: '123456', // Lemon Squeezy Variant ID
|
|
name: 'Pro Plan',
|
|
cost: 29,
|
|
type: 'flat',
|
|
},
|
|
// Cannot add more line items with Lemon Squeezy!
|
|
],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
```
|
|
|
|
{% alert type="warning" title="Single line item only" %}
|
|
The schema validation will fail if you add multiple line items with Lemon Squeezy. This is a platform limitation.
|
|
{% /alert %}
|
|
|
|
## Step 5: Configure Webhooks
|
|
|
|
### Local Development
|
|
|
|
Lemon Squeezy requires a public URL for webhooks. Use a tunneling service like ngrok:
|
|
|
|
```bash
|
|
# Install ngrok
|
|
npm install -g ngrok
|
|
|
|
# Expose your local server
|
|
ngrok http 3000
|
|
```
|
|
|
|
Copy the ngrok URL (e.g., `https://abc123.ngrok.io`).
|
|
|
|
### Create Webhook in Lemon Squeezy
|
|
|
|
1. Go to Settings → Webhooks
|
|
2. Click **Add Webhook**
|
|
3. Configure:
|
|
- **URL**: `https://your-ngrok-url.ngrok.io/api/billing/webhook` (dev) or `https://yourdomain.com/api/billing/webhook` (prod)
|
|
- **Secret**: Generate a secure secret and save it as `LEMON_SQUEEZY_SIGNING_SECRET`
|
|
|
|
4. Select these events:
|
|
- `order_created`
|
|
- `subscription_created`
|
|
- `subscription_updated`
|
|
- `subscription_expired`
|
|
|
|
5. Click **Save**
|
|
|
|
### Production Webhooks
|
|
|
|
For production, replace the ngrok URL with your actual domain:
|
|
|
|
```
|
|
https://yourdomain.com/api/billing/webhook
|
|
```
|
|
|
|
## Metered Usage with Lemon Squeezy
|
|
|
|
Lemon Squeezy handles metered billing differently than Stripe. Usage applies to the entire subscription, not individual line items.
|
|
|
|
### Setup Fee + Metered Usage
|
|
|
|
Use the `setupFee` property for a flat base charge plus usage-based pricing:
|
|
|
|
```tsx
|
|
{
|
|
id: 'api-monthly',
|
|
name: 'API Monthly',
|
|
paymentType: 'recurring',
|
|
interval: 'month',
|
|
lineItems: [
|
|
{
|
|
id: '123456',
|
|
name: 'API Access',
|
|
cost: 0,
|
|
type: 'metered',
|
|
unit: 'requests',
|
|
setupFee: 10, // $10 base fee
|
|
tiers: [
|
|
{ upTo: 1000, cost: 0 },
|
|
{ upTo: 'unlimited', cost: 0.001 },
|
|
],
|
|
},
|
|
],
|
|
}
|
|
```
|
|
|
|
The setup fee is charged once when the subscription is created.
|
|
|
|
### Reporting Usage
|
|
|
|
Report usage using the billing API:
|
|
|
|
```tsx
|
|
import { createBillingGatewayService } from '@kit/billing-gateway';
|
|
|
|
async function reportUsage(subscriptionItemId: string, quantity: number) {
|
|
const service = createBillingGatewayService('lemon-squeezy');
|
|
|
|
return service.reportUsage({
|
|
id: subscriptionItemId, // From subscription_items table
|
|
usage: {
|
|
quantity,
|
|
action: 'increment',
|
|
},
|
|
});
|
|
}
|
|
```
|
|
|
|
See the [metered usage guide](/docs/next-supabase-turbo/billing/metered-usage) for complete implementation details.
|
|
|
|
## Testing
|
|
|
|
### Test Mode
|
|
|
|
Lemon Squeezy has a test mode. Enable it in your dashboard under Settings → Test Mode.
|
|
|
|
Test mode uses separate products and variants, so create test versions of your products.
|
|
|
|
### Test Cards
|
|
|
|
In test mode, use these card numbers:
|
|
- **Success**: `4242 4242 4242 4242`
|
|
- **Decline**: `4000 0000 0000 0002`
|
|
|
|
Any future expiry date and any 3-digit CVC will work.
|
|
|
|
## Common Issues
|
|
|
|
### Webhook signature verification failed
|
|
|
|
1. Check that `LEMON_SQUEEZY_SIGNING_SECRET` matches the secret in your Lemon Squeezy webhook settings
|
|
2. Ensure the raw request body is used for verification (not parsed JSON)
|
|
3. Verify the webhook URL is correct
|
|
|
|
### Subscription not created
|
|
|
|
1. Check webhook logs in Lemon Squeezy dashboard
|
|
2. Verify the `order_created` event is enabled
|
|
3. Check your application logs for errors
|
|
|
|
### Multiple line items error
|
|
|
|
Lemon Squeezy only supports one line item per plan. Restructure your pricing to use a single line item, or use Stripe for more complex pricing models.
|
|
|
|
## Testing Checklist
|
|
|
|
Before going live:
|
|
|
|
- [ ] Create test products in Lemon Squeezy test mode
|
|
- [ ] Test subscription checkout with test card
|
|
- [ ] Verify subscription appears in user's billing section
|
|
- [ ] Test subscription cancellation
|
|
- [ ] Verify webhook events are processed correctly
|
|
- [ ] Test with failing card to verify error handling
|
|
- [ ] Switch to production products and webhook URL
|
|
|
|
## Related Documentation
|
|
|
|
- [Billing Overview](/docs/next-supabase-turbo/billing/overview) - Architecture and provider comparison
|
|
- [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) - Usage-based billing implementation
|