Skip to Content
LaunchfastDocs

Checkout and billing

What happens from the moment someone clicks a pricing button until their plan is unlocked, and how customers manage their billing.

The checkout flow

The visitor clicks a plan

The pricing button posts the plan's slug to the startCheckout server action (src/lib/billing/actions.ts).

Signed out? Sign up first

Visitors who aren't signed in go to /sign-up?plan=<slug>. The chosen plan is kept through sign-up and email confirmation, and the dashboard shows Finish getting [plan] with a Continue to checkout button.

A Dodo customer is found or created

The first checkout creates a Dodo customer for the user and stores the link in the billing_customer table. This is how payments are tied to users. User IDs sent from the browser are never trusted.

Dodo's hosted checkout

The user is sent to Dodo's checkout page to pay. Dodo handles the card form, UPI, tax and receipts.

Back to the dashboard

After paying, Dodo sends the user to /dashboard?checkout=success. The page shows "Payment received. Unlocking your plan, this takes a few seconds…" and checks again every 2.5 seconds.

The webhook unlocks the plan

Meanwhile Dodo sends a webhook to /api/webhooks/dodo. The app records the payment or subscription, and on its next check the dashboard shows the plan. See Webhooks.

If nothing arrives within 60 seconds, the page says: "Your payment went through, but confirming it is taking longer than usual. Please don't pay again." See Plan doesn't unlock after paying.

Who has access

The rule lives in one place, getBillingStatus() in src/lib/billing/index.ts:

  1. A paid one-time purchase gives access for good (until it's fully refunded).
  2. Otherwise, a subscription with status active gives access.
  3. Anything else is the free tier.
Subscription statusAccessDashboard says
activeYes"Renews on [date]." or "Cancels on [date]. You keep access until then."
on_hold (a renewal payment failed)No"Your last payment failed. Update your payment method to keep access."
cancelled, expired, failedNo"Upgrade to unlock everything."

The rule only reads your own database, never Dodo's API, so page loads stay fast. Webhooks keep the tables up to date.

The billing portal

The Manage billing button on the dashboard opens Dodo's customer portal, where customers can update their card, see invoices and cancel subscriptions. Users who never started a checkout are sent to the pricing section instead.

Selling instantly delivered digital goods (downloads, source code) with no refunds to EU or UK buyers? They must agree to give up their 14-day withdrawal right before paying. Set the wording in src/config.ts:

src/config.ts
checkoutConsent: 'I want immediate access and understand I lose my right to withdraw once the download starts.',

A required checkbox then appears before checkout, is checked again on the server, and the time it was accepted is stored on the Dodo order (checkout_consent_at). Leave it null for a normal SaaS. Have the wording reviewed for the countries you sell in.

Upgrading from a subscription to a one-time plan

If a user buys a one-time plan while a subscription is still active, the dashboard warns them that the subscription still renews and asks them to cancel it in Manage billing, so they aren't charged again.

Refunds

Issue refunds in the Dodo dashboard. A full refund marks the payment as refunded, which removes access that came from that one-time purchase (and repository access, if you use it). Partial refunds don't change access.

On this page