ShipFree provides integration with LemonSqueezy as an alternative payment processor, offering a simpler setup for digital products and subscriptions.
Add your LemonSqueezy credentials to .env
:
LEMON_SQUEEZY_API_KEY=your_api_key
LEMON_SQUEEZY_STORE_ID=your_store_id
LEMON_SQUEEZY_WEBHOOK_SECRET=your_webhook_secret
Create and manage products:
import { LemonSqueezy } from "@lemonsqueezy/client";
const ls = new LemonSqueezy(process.env.LEMON_SQUEEZY_API_KEY);
// Fetch products
const products = await ls.getProducts();
// Get specific product
const product = await ls.getProduct(productId);
Create checkout sessions:
// Create a checkout
const checkout = await ls.createCheckout({
storeId: process.env.LEMON_SQUEEZY_STORE_ID,
variantId: "variant_id",
customPrice: 2999, // $29.99
productOptions: {
name: "Custom Plan",
description: "Access to premium features",
},
checkoutOptions: {
dark: true,
logo: "https://yourdomain.com/logo.png",
},
});
// Get checkout URL
const checkoutUrl = checkout.data.attributes.url;
Handle subscriptions:
// Get subscriptions
const subscriptions = await ls.getSubscriptions({
filter: {
userId: "user_123",
},
});
// Update subscription
await ls.updateSubscription(subscriptionId, {
variantId: "new_variant_id",
});
// Cancel subscription
await ls.cancelSubscription(subscriptionId);
Process LemonSqueezy events:
// app/api/webhooks/lemonsqueezy/route.ts
import { verifyWebhook } from "@/lib/lemonsqueezy";
import { headers } from "next/headers";
export async function POST(req: Request) {
const body = await req.text();
const signature = headers().get("X-Signature") as string;
try {
const event = verifyWebhook(
body,
signature,
process.env.LEMON_SQUEEZY_WEBHOOK_SECRET!
);
switch (event.type) {
case "order_created":
await handleOrderCreated(event.data);
break;
case "subscription_created":
await handleSubscriptionCreated(event.data);
break;
case "subscription_updated":
await handleSubscriptionUpdated(event.data);
break;
// Add more event handlers as needed
}
return new Response(null, { status: 200 });
} catch (error) {
console.error("Webhook error:", error);
return new Response("Webhook verification failed", { status: 400 });
}
}
export function LemonSqueezyCheckout({
variantId,
customPrice,
}: {
variantId: string;
customPrice?: number;
}) {
const createCheckout = async () => {
const response = await fetch("/api/create-checkout", {
method: "POST",
body: JSON.stringify({ variantId, customPrice }),
});
const { checkoutUrl } = await response.json();
window.location.href = checkoutUrl;
};
return (
<button
onClick={createCheckout}
className="bg-yellow-400 hover:bg-yellow-500 text-black px-4 py-2 rounded"
>
Buy Now
</button>
);
}
export function SubscriptionStatus({ userId }: { userId: string }) {
const [subscription, setSubscription] = useState(null);
useEffect(() => {
fetchSubscription(userId).then(setSubscription);
}, [userId]);
return (
<div className="p-4 border rounded">
<h3>Subscription Status</h3>
<div className="mt-2">
{subscription ? (
<>
<p>Plan: {subscription.variant.name}</p>
<p>Status: {subscription.status}</p>
<p>
Renews: {new Date(subscription.renews_at).toLocaleDateString()}
</p>
</>
) : (
<p>No active subscription</p>
)}
</div>
</div>
);
}
Security
User Experience
Implementation
Important events to handle:
Order Events
order_created
order_refunded
Subscription Events
subscription_created
subscription_updated
subscription_cancelled
subscription_resumed
subscription_expired
License Events
license_key_created
license_key_updated
try {
// LemonSqueezy operation
} catch (error) {
if (error.response) {
// Handle API errors
console.error("API Error:", error.response.data);
} else if (error.request) {
// Handle network errors
console.error("Network Error:", error.message);
} else {
// Handle other errors
console.error("Error:", error.message);
}
}
// Use test mode in development
const ls = new LemonSqueezy(process.env.LEMON_SQUEEZY_API_KEY, {
testMode: process.env.NODE_ENV === "development",
});
# Use ngrok for local webhook testing
ngrok http 3000