One-time payments
Manage one-time payments with TurboStarter.
While not a typical SaaS billing model, TurboStarter supports one-time (one-off) payments.
One-time payments are useful when you want to sell products that aren't subscription-based, such as:
- Lifetime access: products sold once, granting access forever.
- Multiple purchases: one-off items/add-ons that can be bought multiple times.
Some of this will require custom code (e.g. fulfillment), but TurboStarter provides a solid foundation for handling checkout and syncing successful purchases into your app.
Configuration
One-time payments are represented as variants with model: BillingModel.ONE_TIME in your billing configuration.
export const config = billingConfigSchema.parse({
...
plans: [
{
id: BillingPlan.PREMIUM,
name: "Premium",
description: "Become a power user and gain benefits",
badge: "Bestseller",
features: [
"Unlimited projects",
"Priority support",
"Advanced integrations",
"Team collaboration",
"Analytics dashboard"
],
variants: [
{
/* 👇 This is the `priceId` from the provider (e.g. Stripe), `variantId` (e.g. Lemon Squeezy) or `productId` (e.g. Polar) */
id: "price_1PpUagFQH4McJDTlHCzOmyT6",
cost: 29900,
currency: "usd",
model: BillingModel.ONE_TIME,
},
],
},
],
...
}) satisfies BillingConfig;Let's break down the fields:
id: The unique identifier for the variant. This must match the identifier in the billing provider.cost: The price amount in the smallest currency unit (e.g. cents). Displayed values are typically divided by 100.currency: The currency code (defaults tousd).model: The billing model for the variant. For one-time payments, it must beBillingModel.ONE_TIME.
Please remember that the cost is set for UI purposes. The billing provider handles the actual billing, so make sure the amount is correct in the provider.
Provider notes
- Stripe: one-time purchases typically complete on
checkout.session.completed. Yourvariant.idshould match the Stripe Price ID (e.g.price_...). See Stripe setup. - Lemon Squeezy: one-time purchases typically emit
order_created. Yourvariant.idshould match the Lemon Squeezy Variant ID. See Lemon Squeezy setup. - Polar: one-time purchases typically emit
order.created/order.updated. Yourvariant.idshould match the Polar Product ID (Polar models each “variant” as a separate product). See Polar setup.
When a product is purchased, TurboStarter will create an order in the provider-agnostic order table - you can use this data to fulfill the order and grant access to the product.
How is this guide?
Last updated on