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.
.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=
Development Environment: We recommend putting your Stripe API keys in the .env.local
file.
Go to Customer portal
Active the customer portal link.
Customers can switch plansGo to Customer portal
Enable customers can switch plans and Choose the eligible products that customers can update.
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=
update config set billing_provider = 'lemon-squeezy';
Development Environment: We recommend putting your Lemon Squeezy API keys in the .env.local
file.
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:
}