Payments

SaaSInMinutes uses LemonSqueezy for handling payments and subscriptions. LemonSqueezy is perfect for indie hackers and SaaS founders because it handles tax compliance, VAT, and payment processing automatically.

Why LemonSqueezy?

  • Automatic tax compliance (VAT, sales tax)
  • No need for Stripe Connect or merchant accounts
  • Built-in subscription management
  • One-time and recurring payments
  • Simple API and webhooks

Setup

1. Create a LemonSqueezy account at lemonsqueezy.com

2. Get your API key from Settings → API

3. Add it to your .env.local file:

.env.local
1LEMONSQUEEZY_API_KEY=your-api-key
2LEMONSQUEEZY_STORE_ID=your-store-id

Create a product

Create a checkout URL for your product:

libs/lemonsqueezy.ts
1import { createCheckout } from '@/libs/lemonsqueezy';
2
3export async function createSubscription(userId: string) {
4  const checkout = await createCheckout({
5    storeId: process.env.LEMONSQUEEZY_STORE_ID!,
6    variantId: 'your-variant-id',
7    customPrice: 1999, // $19.99 in cents
8    productOptions: {
9      name: 'SaaSInMinutes Pro',
10      description: 'Monthly subscription',
11    },
12    checkoutOptions: {
13      embed: false,
14      media: false,
15    },
16    previewOptions: {
17      buttonColor: '#60a5fa',
18    },
19    checkoutData: {
20      email: user.email,
21      custom: {
22        user_id: userId,
23      },
24    },
25  });
26
27  return checkout.data.attributes.url;
28}

Handle webhooks

Create a webhook endpoint to handle payment events:

app/api/webhooks/lemonsqueezy/route.ts
1import { NextRequest, NextResponse } from 'next/server';
2import { verifyWebhook } from '@/libs/lemonsqueezy';
3
4export async function POST(req: NextRequest) {
5  const body = await req.text();
6  const signature = req.headers.get('x-signature');
7
8  // Verify webhook signature
9  const isValid = verifyWebhook(body, signature);
10  if (!isValid) {
11    return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
12  }
13
14  const event = JSON.parse(body);
15  
16  switch (event.meta.event_name) {
17    case 'subscription_created':
18      // Handle new subscription
19      await handleSubscriptionCreated(event.data);
20      break;
21    case 'subscription_updated':
22      // Handle subscription update
23      await handleSubscriptionUpdated(event.data);
24      break;
25    case 'subscription_cancelled':
26      // Handle cancellation
27      await handleSubscriptionCancelled(event.data);
28      break;
29  }
30
31  return NextResponse.json({ received: true });
32}

Check subscription status

1import { getSubscription } from '@/libs/lemonsqueezy';
2
3export async function checkSubscription(userId: string) {
4  const subscription = await getSubscription(userId);
5  
6  if (subscription && subscription.status === 'active') {
7    return true;
8  }
9  
10  return false;
11}

Next steps

Learn more about LemonSqueezy subscriptions in our detailed tutorial.