Beztack
Payments

Polar

Payment and subscription system with Polar

Beztack includes Polar as one of the supported payment providers, with Better Auth integration and subscription-oriented flows.

When to use Polar vs Mercado Pago

  • Use Polar when your product is centered on Better Auth checkout/portal workflows and recurring SaaS subscriptions.
  • Use Mercado Pago when you need direct Mercado Pago APIs, plan sync, card processing, and MP-specific events.

See also: Mercado Pago

Setup

Configure the required Polar variables in apps/api/.env:

POLAR_ACCESS_TOKEN=polar_at_xxxxxxxxxxxxx
POLAR_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxx
POLAR_SERVER=sandbox

POLAR_BASIC_MONTHLY_PRODUCT_ID=00000000-0000-0000-0000-000000000000
POLAR_BASIC_YEARLY_PRODUCT_ID=00000000-0000-0000-0000-000000000000
POLAR_PRO_MONTHLY_PRODUCT_ID=00000000-0000-0000-0000-000000000000
POLAR_PRO_YEARLY_PRODUCT_ID=00000000-0000-0000-0000-000000000000
POLAR_ULTIMATE_MONTHLY_PRODUCT_ID=00000000-0000-0000-0000-000000000000
POLAR_ULTIMATE_YEARLY_PRODUCT_ID=00000000-0000-0000-0000-000000000000

POLAR_SUCCESS_URL=http://localhost:5173/checkout-success
POLAR_CANCEL_URL=http://localhost:5173/pricing
POLAR_ORGANIZATION_ID=00000000-0000-0000-0000-000000000000

Implementation references:

  • Better Auth + Polar plugin config: apps/api/server/utils/auth.ts
  • Polar route handlers: apps/api/server/routes/api/polar/*

Core flows

1) Products and pricing data

  • GET /api/polar/products
  • Returns tier-oriented product data from Polar plus local DB feature/limit/ permission metadata.

2) Checkout

  • POST /api/polar/checkout
  • Creates a Polar checkout session for a selected product.

Minimal example:

const response = await fetch("/api/polar/checkout", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ productId: "<polar-product-id>" }),
})

const data = await response.json()
window.location.href = data.checkoutUrl

3) Subscription updates

  • PATCH /api/polar/subscription
  • Validates ownership and updates a subscription product with proration.

4) Customer portal

  • GET /api/polar/customer-portal
  • Returns a customer portal URL from Polar.

5) Usage tracking

Polar usage support is wired through the Better Auth plugin (usage()), so you can report usage events from your app and enforce limits in product logic.

Webhooks and membership enforcement

Webhook endpoint

  • POST /api/polar/webhooks
  • Uses shared webhook validation/processing utilities and updates user/org subscription state for key events (order paid, subscription active, subscription canceled, customer state changed).

Server-side access checks

Use membership utilities in protected API handlers, for example via requireAuth and membership tier checks in apps/api/server/utils/membership.ts.

Common patterns and troubleshooting

  1. Keep product IDs and webhook secrets environment-specific.
  2. Enforce access on the server (not only in the UI).
  3. Use webhook handlers as the source of truth for subscription state changes.
  4. Use sandbox first, then switch to production only when flows are stable.

Quick checks

  • Checkout failing: validate POLAR_ACCESS_TOKEN, product IDs, and payload.
  • Missing webhook effects: validate public endpoint reachability and POLAR_WEBHOOK_SECRET.
  • Incorrect permissions in app: verify membership sync logic and webhook processing logs.

Resources