Loading...
The billing module handles plans, checkout, one-time and lifetime purchases, subscriptions, and the customer portal. It is provider-agnostic with a Stripe-first implementation.
Buyer -> POST /api/v1/subscription/checkout
-> payment provider layer (selected by PAYMENT_PROVIDER)
- stripe -> Stripe Checkout
- lemonsqueezy -> Lemon Squeezy (one-time/lifetime only)
-> returns { url } -> client redirects to hosted checkout
Payment completes -> gateway calls our webhook -> verify -> dedup -> provision
Both providers converge on one provider-agnostic money path, so provisioning logic is not duplicated.
/admin/plans.STRIPE_PRICE_* env fallback./pricing), or a one-time lifetime package.checkout.session.completed, the webhook grants the entitlement once. Provisioning is idempotent: replaying the same event does not double-grant.customer.subscription.updated/deleted, invoice.payment_succeeded/failed) keep the local subscription in sync.PLAN_CHANGE_COOLDOWN_MS (default 24h).charge.refunded, charge.dispute.created) are handled and can trigger access changes.Flip PAYMENT_PROVIDER (and the client NEXT_PUBLIC_PAYMENT_PROVIDER) between stripe and lemonsqueezy. All Stripe code stays intact when Lemon Squeezy is active. Recurring subscriptions, the customer portal, and affiliate clawbacks are Stripe-only.
Open a support ticket with the order or event details.
Sign in to leave feedback on this article.