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:
BILLING_PROVIDER=polarConnect it on your machine
Section titled “Connect it on your machine”Use Polar’s sandbox, where payments are fake. Its tokens are separate from production’s.
-
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=sandboxPOLAR_ACCESS_TOKEN=polar_oat_... -
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 yourAPI_PORT). Leave it running. -
In the sandbox, add a webhook endpoint for
https://<the tunnel's address>/api/billing/webhookwith everysubscription.*event. Put its secret in.envasPOLAR_WEBHOOK_SECRET. The API refuses to start with the token and without this. -
Create your plans in Polar:
Terminal window bun run billing:syncEach 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. -
Restart
bun dev, open Settings → Billing and upgrade with the card4242 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.
Go live
Section titled “Go live”- In production Polar, create the organization access token and the webhook endpoint for
https://<your domain>/api/billing/webhook, with everysubscription.*event. - Set
BILLING_PROVIDER=polar,POLAR_SERVER=production,POLAR_ACCESS_TOKENandPOLAR_WEBHOOK_SECRETin production. A sandbox token or secret will not work. - Run
bun run billing:synconce with the production token in your environment. - 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.
What is different from Stripe
Section titled “What is different from Stripe”- 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.