Skip to main content

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_created
    • subscription_updated
    • subscription_cancelled
    • subscription_expired
    • order_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.js exercises 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 version invoice.subscription and subscription.current_period_{start,end} have moved, and the parser in services/paymentGateways/stripeGateway.js reads them at their old paths. Set your account (or the webhook endpoint) to 2024-12-18.acacia, or update the parser to match.

refund() is not implemented for Stripe — calling it throws. Lemon Squeezy has it.