lemonsqueezy
Integrates payments and subscriptions with Lemon Squeezy merchant of record platform. Use when selling digital products, SaaS subscriptions, or software licenses with built-in tax handling.
When & Why to Use This Skill
This Claude skill integrates the Lemon Squeezy Merchant of Record platform to streamline payment processing and subscription management. It enables developers and businesses to automate the sale of digital products, SaaS licenses, and software subscriptions while handling global tax compliance, billing cycles, and license key distribution directly through AI-driven workflows.
Use Cases
- SaaS Subscription Management: Automate plan upgrades, downgrades, pauses, and cancellations based on user requests or usage metrics.
- Digital Product Sales: Generate secure, customized checkout URLs for one-time purchases or software licenses with built-in global tax handling.
- License Key Orchestration: Programmatically issue, validate, and activate license keys for software applications to manage access and prevent unauthorized use.
- Customer Billing Support: Retrieve order histories, generate invoice PDFs, and manage customer profiles to resolve billing inquiries efficiently.
- Automated Discount Campaigns: Create and manage promotional discount codes and limited-time offers to drive sales growth and customer retention.
- Metered Billing Integration: Report usage records for subscription items to implement flexible, usage-based pricing models.
| name | lemonsqueezy |
|---|---|
| description | Integrates payments and subscriptions with Lemon Squeezy merchant of record platform. Use when selling digital products, SaaS subscriptions, or software licenses with built-in tax handling. |
Lemon Squeezy
Merchant of record platform for digital products and SaaS. Handles payments, taxes, and compliance globally. Official JavaScript SDK for server-side integration.
Quick Start
npm install @lemonsqueezy/lemonsqueezy.js
Setup
import { lemonSqueezySetup, getAuthenticatedUser } from '@lemonsqueezy/lemonsqueezy.js';
lemonSqueezySetup({
apiKey: process.env.LEMONSQUEEZY_API_KEY,
onError: (error) => console.error('Lemon Squeezy Error:', error),
});
// Verify setup
const { data, error } = await getAuthenticatedUser();
if (error) {
console.error('Auth failed:', error.message);
} else {
console.log('Connected as:', data.attributes.name);
}
Create Checkout
Generate checkout URLs for customers to purchase.
import { createCheckout } from '@lemonsqueezy/lemonsqueezy.js';
async function createPaymentCheckout(variantId: string, customerEmail: string) {
const { data, error } = await createCheckout(process.env.LEMONSQUEEZY_STORE_ID!, {
productOptions: {
redirectUrl: 'https://myapp.com/success',
receiptButtonText: 'Go to Dashboard',
receiptThankYouNote: 'Thank you for your purchase!',
},
checkoutOptions: {
embed: true,
media: true,
logo: true,
},
checkoutData: {
email: customerEmail,
custom: {
userId: 'user_123',
},
},
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString(), // 24 hours
preview: false,
}, {
relationships: {
variant: {
data: {
type: 'variants',
id: variantId,
},
},
},
});
if (error) throw new Error(error.message);
return data.attributes.url;
}
With Discount
const { data } = await createCheckout(storeId, {
checkoutData: {
discountCode: 'SAVE20',
email: customerEmail,
},
}, {
relationships: {
variant: {
data: { type: 'variants', id: variantId },
},
},
});
Products & Variants
import { listProducts, listVariants, getProduct, getVariant } from '@lemonsqueezy/lemonsqueezy.js';
// List all products
const { data: products } = await listProducts({
filter: { storeId: process.env.LEMONSQUEEZY_STORE_ID },
});
products.forEach((product) => {
console.log(product.attributes.name, product.attributes.price_formatted);
});
// Get single product
const { data: product } = await getProduct('prod_123');
// List variants for a product
const { data: variants } = await listVariants({
filter: { productId: 'prod_123' },
});
// Get variant details
const { data: variant } = await getVariant('var_123');
Subscriptions
import {
listSubscriptions,
getSubscription,
updateSubscription,
cancelSubscription,
} from '@lemonsqueezy/lemonsqueezy.js';
// List user's subscriptions
const { data: subscriptions } = await listSubscriptions({
filter: {
storeId: process.env.LEMONSQUEEZY_STORE_ID,
userEmail: 'customer@example.com',
},
});
// Get subscription details
const { data: subscription } = await getSubscription('sub_123');
console.log('Status:', subscription.attributes.status);
console.log('Renews at:', subscription.attributes.renews_at);
// Pause subscription
await updateSubscription('sub_123', {
pause: {
mode: 'void', // or 'free'
resumesAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
},
});
// Resume subscription
await updateSubscription('sub_123', {
pause: null,
});
// Change plan (upgrade/downgrade)
await updateSubscription('sub_123', {
variantId: 'new_variant_id',
invoiceImmediately: true,
});
// Cancel subscription
await cancelSubscription('sub_123');
License Keys
import {
listLicenseKeys,
getLicenseKey,
updateLicenseKey,
activateLicense,
validateLicense,
deactivateLicense,
} from '@lemonsqueezy/lemonsqueezy.js';
// List license keys
const { data: licenses } = await listLicenseKeys({
filter: { orderId: 'order_123' },
});
// Activate license (no API key required)
const { data: activation } = await activateLicense('LICENSE-KEY-HERE', 'instance-name');
console.log('License valid:', activation.valid);
console.log('Activations:', activation.license_key.activation_usage);
// Validate license (no API key required)
const { data: validation } = await validateLicense('LICENSE-KEY-HERE', 'instance-name');
if (validation.valid) {
console.log('License is active');
} else {
console.log('Invalid:', validation.error);
}
// Deactivate license (no API key required)
await deactivateLicense('LICENSE-KEY-HERE', 'instance-id');
// Disable/enable license key (requires API key)
await updateLicenseKey('lk_123', {
disabled: true,
});
Orders
import { listOrders, getOrder, listOrderItems, generateOrderInvoice } from '@lemonsqueezy/lemonsqueezy.js';
// List orders
const { data: orders } = await listOrders({
filter: {
storeId: process.env.LEMONSQUEEZY_STORE_ID,
userEmail: 'customer@example.com',
},
});
// Get order details
const { data: order } = await getOrder('order_123');
console.log('Total:', order.attributes.total_formatted);
console.log('Status:', order.attributes.status);
// Get order items
const { data: items } = await listOrderItems({
filter: { orderId: 'order_123' },
});
// Generate invoice PDF
const { data: invoice } = await generateOrderInvoice('order_123');
console.log('Invoice URL:', invoice.attributes.url);
Customers
import {
createCustomer,
getCustomer,
listCustomers,
updateCustomer,
archiveCustomer,
} from '@lemonsqueezy/lemonsqueezy.js';
// Create customer
const { data: customer } = await createCustomer(process.env.LEMONSQUEEZY_STORE_ID!, {
name: 'John Doe',
email: 'john@example.com',
city: 'New York',
country: 'US',
});
// List customers
const { data: customers } = await listCustomers({
filter: { storeId: process.env.LEMONSQUEEZY_STORE_ID },
});
// Update customer
await updateCustomer('cust_123', {
name: 'Jane Doe',
});
// Archive customer
await archiveCustomer('cust_123');
Discounts
import {
createDiscount,
listDiscounts,
getDiscount,
deleteDiscount,
} from '@lemonsqueezy/lemonsqueezy.js';
// Create discount
const { data: discount } = await createDiscount(process.env.LEMONSQUEEZY_STORE_ID!, {
name: 'Summer Sale',
code: 'SUMMER2024',
amount: 20,
amountType: 'percent', // or 'fixed'
isLimitedToProducts: false,
isLimitedRedemptions: true,
maxRedemptions: 100,
startsAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
});
// List discounts
const { data: discounts } = await listDiscounts({
filter: { storeId: process.env.LEMONSQUEEZY_STORE_ID },
});
// Delete discount
await deleteDiscount('disc_123');
Webhooks
Setup Webhook Handler
import { createWebhook, listWebhooks } from '@lemonsqueezy/lemonsqueezy.js';
import crypto from 'crypto';
// Create webhook programmatically
const { data: webhook } = await createWebhook(process.env.LEMONSQUEEZY_STORE_ID!, {
url: 'https://myapp.com/api/webhooks/lemonsqueezy',
events: [
'order_created',
'subscription_created',
'subscription_updated',
'subscription_cancelled',
'subscription_payment_success',
'subscription_payment_failed',
'license_key_created',
],
secret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET,
});
Handle Webhooks (Next.js)
// app/api/webhooks/lemonsqueezy/route.ts
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';
export async function POST(request: NextRequest) {
const rawBody = await request.text();
const signature = request.headers.get('x-signature');
// Verify signature
const hmac = crypto.createHmac('sha256', process.env.LEMONSQUEEZY_WEBHOOK_SECRET!);
const digest = hmac.update(rawBody).digest('hex');
if (signature !== digest) {
return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
}
const payload = JSON.parse(rawBody);
const eventName = payload.meta.event_name;
const data = payload.data;
switch (eventName) {
case 'order_created':
console.log('New order:', data.id);
// Fulfill order, send email, etc.
break;
case 'subscription_created':
console.log('New subscription:', data.id);
// Grant access, update database
break;
case 'subscription_updated':
console.log('Subscription updated:', data.attributes.status);
// Handle plan changes, pauses
break;
case 'subscription_cancelled':
console.log('Subscription cancelled:', data.id);
// Revoke access, send win-back email
break;
case 'subscription_payment_success':
console.log('Payment received');
// Update billing records
break;
case 'subscription_payment_failed':
console.log('Payment failed');
// Notify customer, retry logic
break;
case 'license_key_created':
console.log('License created:', data.attributes.key);
// Send license to customer
break;
}
return NextResponse.json({ received: true });
}
Lemon.js (Frontend)
For client-side checkout overlays.
<script src="https://app.lemonsqueezy.com/js/lemon.js" defer></script>
<a href="https://mystore.lemonsqueezy.com/checkout/buy/abc123" class="lemonsqueezy-button">
Buy Now
</a>
With JavaScript
// Initialize
window.createLemonSqueezy();
// Open checkout programmatically
window.LemonSqueezy.Url.Open('https://mystore.lemonsqueezy.com/checkout/buy/abc123');
// Listen to events
window.LemonSqueezy.Setup({
eventHandler: (event) => {
if (event.event === 'Checkout.Success') {
console.log('Purchase complete!', event.data);
}
},
});
Usage Records (Metered Billing)
import { createUsageRecord, listUsageRecords } from '@lemonsqueezy/lemonsqueezy.js';
// Report usage
const { data: record } = await createUsageRecord({
subscriptionItemId: 'si_123',
quantity: 100,
action: 'increment', // or 'set'
});
// List usage records
const { data: records } = await listUsageRecords({
filter: { subscriptionItemId: 'si_123' },
});
Next.js Server Actions
// app/actions/billing.ts
'use server';
import {
createCheckout,
cancelSubscription,
updateSubscription,
} from '@lemonsqueezy/lemonsqueezy.js';
import { auth } from '@/lib/auth';
export async function createCheckoutAction(variantId: string) {
const session = await auth();
if (!session?.user) throw new Error('Unauthorized');
const { data, error } = await createCheckout(
process.env.LEMONSQUEEZY_STORE_ID!,
{
checkoutData: {
email: session.user.email,
custom: { userId: session.user.id },
},
},
{
relationships: {
variant: { data: { type: 'variants', id: variantId } },
},
}
);
if (error) throw new Error(error.message);
return data.attributes.url;
}
export async function cancelSubscriptionAction(subscriptionId: string) {
const session = await auth();
if (!session?.user) throw new Error('Unauthorized');
// Verify ownership before cancelling
await cancelSubscription(subscriptionId);
return { success: true };
}
Environment Variables
LEMONSQUEEZY_API_KEY=your_api_key
LEMONSQUEEZY_STORE_ID=your_store_id
LEMONSQUEEZY_WEBHOOK_SECRET=your_webhook_secret
Best Practices
- Server-side only - Never expose API key in browser
- Verify webhooks - Always validate signatures
- Use test mode - Build integration before going live
- Store custom data - Pass userId in checkout for correlation
- Handle all events - Account for failed payments, cancellations
- Idempotent handlers - Webhooks may be delivered multiple times