Skip to content

Polar

Polar is the other payment provider the kit supports. It is the merchant of record: it charges sales tax and VAT, files them, and sends receipts and invoices itself. Pick it when you would rather not deal with tax; pick Stripe for the most control and the lowest fees. Everything else in the kit is the same with either: plans, feature gates, seats, the billing page and the mobile app. How billing works is on Billing.

Choose it in .env, and leave Stripe’s variables empty:

Terminal window
BILLING_PROVIDER=polar

Use Polar’s sandbox, where payments are fake. Its tokens are separate from production’s.

  1. Create an organization in the sandbox, then an organization access token with access to products, checkouts, customers, members, customer sessions, subscriptions, meters and events. Put it in .env:

    Terminal window
    POLAR_SERVER=sandbox
    POLAR_ACCESS_TOKEN=polar_oat_...
  2. Polar cannot reach your machine, so give it an address that can. A quick tunnel needs no account: cloudflared tunnel --url http://localhost:3000 (use your API_PORT). Leave it running.

  3. In the sandbox, add a webhook endpoint for https://<the tunnel's address>/api/billing/webhook with every subscription.* event. Put its secret in .env as POLAR_WEBHOOK_SECRET. The API refuses to start with the token and without this.

  4. Create your plans in Polar:

    Terminal window
    bun run billing:sync

    Each paid plan becomes two products, monthly and yearly. Each meter a plan charges for becomes a Polar meter, and a metered price on those products. Run it again whenever you change PLANS.

  5. Restart bun dev, open Settings → Billing and upgrade with the card 4242 4242 4242 4242, any future date and any CVC.

The plan changes a moment after you return from Polar, when the webhook arrives. If it does not, the tunnel or the endpoint is wrong: Polar’s dashboard shows each delivery and its answer.

  1. In production Polar, create the organization access token and the webhook endpoint for https://<your domain>/api/billing/webhook, with every subscription.* event.
  2. Set BILLING_PROVIDER=polar, POLAR_SERVER=production, POLAR_ACCESS_TOKEN and POLAR_WEBHOOK_SECRET in production. A sandbox token or secret will not work.
  3. Run bun run billing:sync once with the production token in your environment.
  4. Buy a plan on the live site with a real card, change it in the portal, and cancel it.

Step 4 proves your own setup: your token, your webhook and your products together. Do it before you announce.

  • Receipts and tax come from Polar. The kit sends no receipt email.
  • Customers. Each workspace is a Polar customer, found by the workspace’s id. With teams on it is a team customer owned by the person who checked out, so one person can pay for several workspaces. Its portal always opens as that owner, whichever admin clicks Manage.
  • Changing a price replaces the product’s price. People already subscribed keep the price they bought.
  • Usage beyond a plan’s allowance is sent as events named after the meter, which Polar’s meter sums. See Charge for usage.
  • Locally, there is no billing:listen: use a tunnel, as above.

If something does not work, see Troubleshooting.