Dokumentation
Zahlungen (Polar)
Polar-Billing in Motoko Base — Setup, Checkout, Portal, Webhooks und Entitlements.
Motoko Base nutzt Polar für Abonnements in v1. Billing ist in einer anbieteragnostischen API gekapselt — Feature-Code importiert Polar nie direkt.
Official docs: Polar documentation
Architecture
Polar (source of truth)
↓
polar.customers.getStateExternal(userId)
↓
BillingSummary (plan, status, period end)
↓
Dashboard UI + hasPlan() / canAccess()Polar ist die Quelle der Wahrheit. Motoko Base spiegelt Abonnements nicht in Postgres. Der Plan-Status wird aus dem Polar Customer State geholt und kurz gecacht (60s). Webhooks invalidieren den Cache bei Abonnement-Änderungen.
Better Auth user id = Polar externalId. Ein Polar-Kunde wird beim Sign-up erstellt, wenn Billing konfiguriert ist.
Setup
- Erstelle eine Polar-Organisation — starte im sandbox
- Erstelle ein Pro-Abonnementprodukt → Product-ID kopieren
- Erstelle ein Organization Access Token
- Füge einen Webhook hinzu →
{APP_URL}/api/billing/webhooks/polar - Setze Env-Vars in
.env:
| Variable | Purpose |
|---|---|
BILLING_PROVIDER | polar (default) |
POLAR_ACCESS_TOKEN | Organization access token |
POLAR_WEBHOOK_SECRET | Webhook signing secret |
POLAR_PRO_MONTHLY_PRODUCT_ID | Pro monthly product ID |
POLAR_PRO_YEARLY_PRODUCT_ID | Pro yearly product ID (optional) |
POLAR_SERVER | sandbox or production |
Ohne Polar-Env-Vars bleibt die App auf Free — kein Crash, Checkout deaktiviert.
Checkout & portal
| Action | API | UI |
|---|---|---|
| Upgrade to Pro | checkout(userId, "pro") | Billing page → Upgrade |
| Manage subscription | openPortal(userId) | Billing page → Manage billing |
Checkout leitet zu Polar Checkout weiter. Das Portal öffnet das Polar Customer Portal (kündigen, Zahlungsmethode aktualisieren, Rechnungen).
Webhooks
Route: src/app/api/billing/webhooks/polar/
Polar sendet Abonnement-Events → Signatur geprüft → Billing-Cache invalidiert → Analytics-Event erfasst. Kein lokales Abonnement-Ledger.
Abonniere Abonnement-Events + customer.state_changed im Polar-Dashboard. Unbehandelte Event-Typen werden sicher ignoriert.
Entitlements
import { hasPlan, canAccess } from "@/lib/billing";
await hasPlan(userId, "pro");
await canAccess(userId, "ai"); // feature → plan map in billingConfigDas Feature→Plan-Mapping liegt in src/lib/billing/config.ts. Demo-Features sind in v1 nicht gated — rufe canAccess auf, wenn du bezahlte Gates hinzufügst.
Plan-Labels in der UI: src/lib/billing/config/plans.ts.
Sandbox testing
- Setze
POLAR_SERVER=sandboxund Sandbox-Token/Product-ID - Nutze Polar-Testkarten im Checkout — siehe Polar sandbox docs
- Bestätige die Webhook-Zustellung im Polar-Dashboard (lokal ggf. ngrok)
- Nach dem Checkout
/dashboard/settings/administration/billing?checkout=successöffnen — der Plan sollte aktualisiert werden
Wechsle zu POLAR_SERVER=production mit Produktions-Credentials für Live-Billing.
Where to change this
| What | Where |
|---|---|
| Public billing API | src/lib/billing/index.ts |
| Plans & feature gates | src/lib/billing/config.ts |
| Polar provider | src/lib/billing/providers/polar/ |
| Webhook handler | src/lib/billing/providers/polar/webhooks.ts |
| Billing UI & actions | src/features/dashboard/actions/billing.ts, src/features/dashboard/components/settings/billing-* |
Importiere @polar-sh/* nicht aus Features oder UI — nutze nur @/lib/billing.
Environment Variables — alle Polar-Env-Vars.