Billing

This page details the billing providers available in the ZTO SaaS Starter Kit. We cover Stripe and Lemon Squeezy billing providers, and how to enable them in your project.

The core code for billing is located in the packages/billing directory.

  • core: Contains the core billing logic.
  • geteway: Contains the billing provider logic.
  • stripe: Contains the Stripe billing provider logic.
  • lemon-squeezy: Contains the Lemon Squeezy billing provider logic.
Payment provider setup:

.env

# Billing provider (stripe/lemon-squeezy)
NEXT_PUBLIC_BILLING_PROVIDER = stripe 

Stripe

To use Stripe as your billing provider, you need to create a Stripe account and get your API keys.

Stripe setup:
# Stripe API keys
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
Active the customer portal link

Go to Customer portal

Active the customer portal link.

Screenshot of Enable portal link Customers can switch plans

Go to Customer portal

Enable customers can switch plans and Choose the eligible products that customers can update.

Screenshot of Customers can switch plans

Learn more about Stripe docs.

Lemon Squeezy

To use Lemon Squeezy as your billing provider, you need to create a Lemon Squeezy account and get your API keys.

Lemon Squeezy env setup:
# Lemon Squeezy API keys
LEMON_SQUEEZY_SECRET_KEY=
LEMON_SQUEEZY_SIGNING_SECRET=
LEMON_SQUEEZY_STORE_ID=
Lemon Squeezy supabase setup:
update config set billing_provider = 'lemon-squeezy';

Learn more about Lemon Squeezy docs.

Subscriptions or One-time Payments

You can set up subscriptions or one-time payments in the .env file. If the BILLING_MODE is not exist, the default value is subscription.

.env

# Billing mode (subscription/one-time)
BILLING_MODE=subscription

Plans

You can set up plans in the apps/web/config/billing.config.ts file located in the apps/web/config directory.

Before you start, you need to set the NEXT_PUBLIC_BILLING_PROVIDER in the .env file.

.env

# Billing provider (stripe/lemon-squeezy)
NEXT_PUBLIC_BILLING_PROVIDER=stripe

Here is an example of the plans Configuration:

Demo Billing Config

{
  provider,
  products: [
    {
      id: 'starter', // unique product id, you can set any value
      name: 'Starter', // product name
      description: 'The perfect plan to get started', // product description
      currency: 'USD', // currency
      plans: [ // plans
        {
          name: 'Starter Monthly',  // plan name
          id: 'starter-monthly', // unique plan id, you can set any value
          paymentType: 'recurring', // payment type (recurring/one-time)
          interval: 'month', // interval (month/year)
          lineItems: [ 
            {
              id: '470182', // stripe price id or lemon squeezy price id
              name: 'Addon 2', // item name
              cost: 30.00, // item cost
              type: 'flat' as const, // item type (flat/percentage)
            },
          ],
        },
        {
          name: 'Starter Yearly', // plan name
          id: 'starter-yearly', // unique plan id, you can set any value
          paymentType: 'recurring', // payment type (recurring/one-time)
          interval: 'year', // interval (month/year)
          lineItems: [ 
            {
              id: 'starter-yearly',  // stripe price id or lemon squeezy price id
              name: 'Base', // item name
              cost: 99.99, // item cost
              type: 'flat' as const, // item type (flat/percentage)
            },
          ],
        },
      ],
      features: ['Feature 1', 'Feature 2', 'Feature 3'], // product features
    },
    ...
  ],
}

Payment Webhooks

Webhooks are a way for your application to receive real-time notifications from [Stripe] about events that happen in your account, such as payments, disputes, and more.

The webhook endpoint is located at apps/web/app/api/billing/webhook/route.ts. You can add your own logic to handle the webhook events.

The webhook handler is located at packages/billing/gateway/src/server/services/billing-event-handler directory.

Stripe Webhook Handler

switch (event.type) {
    case 'checkout.session.completed': 

    case 'customer.subscription.updated': 

    case 'customer.subscription.deleted':

    case 'checkout.session.async_payment_failed': 

    case 'checkout.session.async_payment_succeeded':

    case 'invoice.paid': 

    default: 
}

Lemon Squeezy Webhook Handler

switch (eventName) {
    case 'order_created':

    case 'subscription_created':

    case 'subscription_updated':

    case 'subscription_expired':

    case 'subscription_payment_success':

    default: 
}