Lemon Squeezy Setup
Prime SaaS supports multiple payment gateways. Lemon Squeezy is the default recommended option — it handles EU VAT, handles payouts, and has a simpler webhook model than Stripe.
1. Create a Lemon Squeezy account
Sign up at app.lemonsqueezy.com. Create a store if you haven't already.
2. Get your API key
Go to Settings → API and generate a new API key.
3. Note your Store ID
Your Store ID is visible in the URL when you're on your store dashboard:
https://app.lemonsqueezy.com/stores/YOUR_STORE_ID/...
4. Create your products and variants
In Lemon Squeezy, create a Product for each pricing plan. Each plan needs one Variant for monthly billing.
Note the Variant IDs — you'll need them to populate your plans database table.
5. Create a webhook
Go to Settings → Webhooks and add a new webhook:
- URL:
https://yourdomain.com/webhooks/lemonsqueezy - Events to listen for:
subscription_createdsubscription_updatedsubscription_cancelledsubscription_expiredorder_created
Copy the Signing Secret — you'll use it as LEMONSQUEEZY_WEBHOOK_SECRET.
For local development, use a tunnel like ngrok:
ngrok http 6600
# Use the generated HTTPS URL as your webhook URL
6. Configure environment variables
In demo-server/.env:
PAYMENT_GATEWAY=lemonsqueezy
LEMONSQUEEZY_API_KEY=your_api_key_here
LEMONSQUEEZY_STORE_ID=your_store_id_here
LEMONSQUEEZY_WEBHOOK_SECRET=your_webhook_signing_secret
# Optional: give test accounts a discount at checkout
TEST_ACCOUNT_IDS=123,456
TEST_ACCOUNT_DISCOUNT_CODE=TESTDISCOUNT
7. Populate plans in the database
After running migrations, update each row in the plans table with the Lemon Squeezy product and variant IDs:
UPDATE plans
SET
lemonsqueezy_product_id = 'your_product_id',
lemonsqueezy_monthly_variant_id = 'your_monthly_variant_id'
WHERE name = 'Pro';
Switching payment gateways
Two gateways ship: lemonsqueezy (the default) and stripe.
PAYMENT_GATEWAY=stripe
The server uses a factory pattern (demo-server/services/paymentGateways/), so
checkout, subscription management, plan changes and webhook handling all route
through whichever adapter is selected — no code changes needed to switch. You do
need to fill in that gateway's price/variant IDs on each plan row and register its
webhook endpoint.
Stripe status
Stripe implements the full gateway interface — checkout, cancel, resume, plan change with proration, billing portal, invoices and webhooks. Two caveats before you run it in production:
- No integration test coverage.
test/api/subscriptionLifecycle.test.jsexercises the lifecycle against Lemon Squeezy fixtures only. The handlers it covers are gateway-agnostic, but the Stripe adapter's payload parsing is not yet asserted. - Pin your Stripe API version. The SDK is pinned to
2024-12-18.acacia, but webhook payloads arrive in your Stripe account's default version, which the SDK does not transform. On a newer versioninvoice.subscriptionandsubscription.current_period_{start,end}have moved, and the parser inservices/paymentGateways/stripeGateway.jsreads them at their old paths. Set your account (or the webhook endpoint) to2024-12-18.acacia, or update the parser to match.
refund() is not implemented for Stripe — calling it throws. Lemon Squeezy has it.