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-idCreate 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.