LemonSqueezy is used to handle payments. It has built-in VAT/sales tax compliance, affiliates & marketing tools.

Set up LemonSqueezy

First, create an LemonSqueezy account or sign in. Create a store & add your digital products. It usually takes 1-2 days for it to get verified.

Once that is ready, we’ll set up the app to handle webhooks from LemonSqueezy:

  1. Go to Settings -> Webhooks and add a new Webhook

    Add a new webhook to LemonSqueezy

    Add a new webhook to LemonSqueezy

  2. Fill in Callback URL with https://yourwebsite.com/api/payments/lemonsqueezy & select all events.

  3. Add your defined LEMON_SQUEEZY_WEBHOOK_SECRET to your .env file.

    Select all events & specify your LEMON_SQUEEZY_WEBHOOK_SECRET

    Select all events & specify your LEMON_SQUEEZY_WEBHOOK_SECRET

Handling webhooks

Under app/api/payments/lemonsqueezy/route.ts you’ll find an API route that handles both one-time-payments and subscriptions from LemonSqueezy.

It will do the following:

  1. Verify if the request is legitimate by matching LEMON_SQUEEZY_WEBHOOK_SECRET
  2. Get purchased product, based on the variant_id
  3. Match the user email with our profiles table
    • If there is a matching user, it will edit purchase column of the matching user in the profiles table based on the logic you specify in the webhook. Feel free to add any logic here you’ll use in your app to handle page access.
    • If no matching user is found, it will simply update the purchases table with a new entry

Storing webhook data

We’ll store all webhook data coming from LemonSqueezy in our own database as well.

Copy-paste the following your Supabase SQL editor:

create table
  public.purchases (
    id bigint generated by default as identity,
    user_email text null,
    type text null,
    created_at timestamp with time zone null default now(),
    purchase_id text null,
    payload jsonb null,
    constraint purchases_pkey primary key (id)
  ) tablespace pg_default;

Then, turn on RLS. We don’t need to add any rules as we’ll be using the service key for to interact with this table.

Set up paywall

All demo apps now include a paywall using a credit system. You can modify the logic in app/api/payments/lemonsqueezy/route.ts to add more credits after a succesfull purchase.

You can adjust the code to handle access based on the purchase column having a specific value. This allows for handling lifetime access systems or subscriptions.

Logic flow

  1. Check if the demo app is paywalled (specified in toolConfig.ts).
  2. Verify if the user has more credits than required for one generation:
    • If yes, grant access.
    • If no, display the PaymentModal component.
audio/app/page.tsx
let credits;
if (toolConfig.paywall) {
  const { data: profile } = await supabase
    .from("profiles")
    .select("*")
    .eq("id", user.id)
    .single();

  credits = profile.credits;

  console.table(profile);

  if (credits < toolConfig.credits) {
    return <PaymentModal />;
  }
}