Skip to main content
If you are new to payments, think about this system in two parts:
  1. a checkout link that sends the user to Stripe, LemonSqueezy, or Polar
  2. a webhook that tells your app “the payment succeeded”

What The Payments System Does

In AnotherWrapper, payments are used for two main things:
  • selling your product tiers such as Premium, Enterprise, or Agency
  • selling credit top-ups such as credits-small and credits-large
The flow is simple:
  1. A user clicks a checkout button in your app
  2. They pay on the provider’s hosted checkout page
  3. The provider sends a webhook to your app
  4. Your app verifies the webhook is real
  5. Your app stores the purchase in the database
  6. Your app updates the user’s credits or purchase state

Important Basic Terms

Checkout URL

A checkout URL is the payment link your user clicks. Example:
https://buy.stripe.com/...
https://yourstore.lemonsqueezy.com/buy/...
https://polar.sh/checkout/...
You put these URLs in env vars so the UI can use them without hardcoding them.

Webhook

A webhook is a secure HTTP request sent by your payment provider to your app after something important happens, such as a successful payment. In this repo, all payment providers send webhooks to:
  • Stripe: https://yourdomain.com/api/payments/stripe
  • LemonSqueezy: https://yourdomain.com/api/payments/lemonsqueezy
  • Polar: https://yourdomain.com/api/payments/polar

Purchase Type

A purchase type is the label your app uses internally to know what was bought. The current repo supports:
  • anotherwrapper-premium
  • anotherwrapper-enterprise
  • anotherwrapper-agency
  • credits-small
  • credits-large
Your payment provider may call something a “product”, “variant”, or “price”, but inside this app it eventually has to map to one of those purchase types.

What You Need Before Starting

Before setting up payments, make sure:
  • the app already runs locally
  • Supabase is set up
  • your production domain is known
  • you know which provider you want to use first
Do not start with all three providers at once. Pick one provider, get it working fully, then expand later if you want.

Shared Env Vars

These env vars are used regardless of provider:
NEXT_PUBLIC_PAYMENT_PROVIDER=stripe
NEXT_PUBLIC_DEFAULT_MARKETING_PURCHASE_TYPE=anotherwrapper-enterprise
NEXT_PUBLIC_CHECKOUT_URL_TEMPLATE=https://...
NEXT_PUBLIC_CHECKOUT_URL_CREDITS_SMALL=https://...
NEXT_PUBLIC_CHECKOUT_URL_CREDITS_LARGE=https://...
Optional checkout URLs:
NEXT_PUBLIC_CHECKOUT_URL_PREMIUM=https://...
NEXT_PUBLIC_CHECKOUT_URL_ENTERPRISE=https://...
NEXT_PUBLIC_CHECKOUT_URL_AGENCY=https://...
NEXT_PUBLIC_AFFILIATES_URL=https://...

What Each Shared Env Var Means

  • NEXT_PUBLIC_PAYMENT_PROVIDER: which provider the UI should use
  • NEXT_PUBLIC_DEFAULT_MARKETING_PURCHASE_TYPE: which plan the main marketing CTA should represent
  • NEXT_PUBLIC_CHECKOUT_URL_TEMPLATE: fallback main checkout URL
  • NEXT_PUBLIC_CHECKOUT_URL_CREDITS_SMALL: checkout link for the small credit pack
  • NEXT_PUBLIC_CHECKOUT_URL_CREDITS_LARGE: checkout link for the large credit pack
If you want cleaner pricing buttons, also set the specific Premium/Enterprise/Agency URLs.

How The Repo Handles Payments

The main files are:
  • lib/payments/public-config.ts for shared checkout URLs, labels, prices, and credit-pack metadata
  • components/(ui-components)/payments/checkout-link.tsx for hosted checkout buttons in the UI
  • app/api/payments/[provider]/route.ts for the shared webhook entrypoint
  • lib/payments/providers/* for provider-specific verification logic
  • lib/payments/processor.ts for the shared “payment succeeded” logic
That means the UI and the business logic are mostly shared, while the webhook verification differs per provider. Real checkout URLs should stay in env. Shared purchase display metadata should stay in lib/payments/public-config.ts. UI components should not hardcode raw checkout links.

Metadata vs Product Maps

There are two common ways to tell the app what a payment means.

Option A: Metadata

You store a value like type=anotherwrapper-enterprise in the provider itself. This is usually the cleanest approach because the purchase explains itself.

Option B: Product Map In Env

You keep a JSON map in env:
STRIPE_PRODUCT_MAP='{"prod_123":"anotherwrapper-enterprise"}'
POLAR_PRODUCT_MAP='{"prod_123":"credits-small"}'
LEMON_SQUEEZY_VARIANT_MAP='{"123456":"credits-large"}'
This is useful if you want to change mappings without changing code.

Credits

If the purchase type is credits-small or credits-large, the repo can add credits automatically after the webhook succeeds. If the purchase is a plan like anotherwrapper-enterprise, the repo stores the purchase and updates the user’s profile state instead.

Optional Meta Ads Attribution

If you run Meta Ads, the repo can also track:
  • InitiateCheckout from the shared checkout link component
  • Purchase from the shared payment processor after a successful webhook
Enable it only if you actually need it:
NEXT_PUBLIC_ENABLE_META_ATTRIBUTION=true
NEXT_PUBLIC_META_PIXEL_ID=...
META_ACCESS_TOKEN=...

Suggested Setup Order

For beginners, use this order:
  1. Pick one provider
  2. Create one simple “Premium” product
  3. Create one simple “Small credits” product
  4. Add the checkout URLs to .env.local
  5. Add the webhook endpoint in the provider dashboard
  6. Complete a test payment
  7. Confirm the purchase appears in your database
  8. Confirm the user gets the right result in the app

Common Mistakes

  • using the wrong webhook URL
  • forgetting to set the provider-specific secret
  • creating checkout links but never connecting the webhook
  • not mapping the purchased product to a valid purchase type
  • changing .env.local but forgetting to restart the app

Provider Guides

Meta Ads

See how the repo optionally tracks checkout clicks and purchases for Meta Ads.

Credits And Billing

See how credits are used in the dashboard and across the apps.