Where Payment Configuration Lives
All paid plans are defined in config/pricing.ts.
The free plan keeps empty gateway IDs, while pro and enterprise already include monthly and yearly IDs for Stripe, Lemon Squeezy, and Paddle.
variantIds: {
stripe: { monthly: "price_xxx", yearly: "price_yyy" },
lemonsqueezy: { monthly: "123", yearly: "456" },
paddle: { monthly: "pri_xxx", yearly: "pri_yyy" },
}
Authenticated App Billing Flow
- User opens /pricing inside the app.
- The checkout page calls createSubscriptionCheckout() from actions/payment/checkout.ts.
- The action verifies Supabase auth and also verifies that the user belongs to the selected organization.
- Stripe returns a hosted checkout URL, Lemon Squeezy returns a hosted checkout URL, and Paddle returns a client checkout payload.
- After success, the app lands on /pricing/success and final sync happens through Stripe session confirmation or provider webhook processing.
Landing Billing Flow
- Public checkout starts from landing-app/views/Checkout.tsx.
- That page calls createLandingUserSubscriptionCheckout() from actions/payment/landing-checkout.ts.
- Stripe redirects directly to hosted checkout, Lemon Squeezy creates a hosted checkout URL, and Paddle opens the overlay with @paddle/paddle-js.
- After success, the landing flow redirects users to NEXT_PUBLIC_JAMPACK_APP_URL/dashboard?source=landing&status=success&plan=....
- Stripe landing purchases are confirmed with confirmLandingStripeCheckoutSession(), while Paddle and Lemon Squeezy are marked active optimistically and then kept in sync by webhooks.
Webhook Routes
Each provider has its own route handler under the parent app project.
All three routes update billing state on both Profile and Organization records when enough metadata is present.
Stripe additionally maintains Subscription and OrganizationSubscription tables with recurring period data.
- app/api/webhooks/stripe/route.ts
- app/api/webhooks/paddle/route.ts
- app/api/webhooks/lemonsqueezy/route.ts
Recommended Setup Order
- Confirm DATABASE_URL, DIRECT_URL, and Supabase auth are working first.
- Review all IDs in config/pricing.ts.
- Enable exactly one provider first.
- Test app checkout at /pricing.
- Test landing checkout at /landing/checkout.
- Verify webhook delivery and billing updates in Prisma.