Dokumentation

Zahlungen (Polar)

Polar-Billing in Motoko Base — Setup, Checkout, Portal, Webhooks und Entitlements.

Öffnen inChatGPT (öffnet in neuem Tab)Claude (öffnet in neuem Tab)Cursor (öffnet in neuem Tab)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

  1. Erstelle eine Polar-Organisation — starte im sandbox
  2. Erstelle ein Pro-Abonnementprodukt → Product-ID kopieren
  3. Erstelle ein Organization Access Token
  4. Füge einen Webhook hinzu → {APP_URL}/api/billing/webhooks/polar
  5. Setze Env-Vars in .env:
VariablePurpose
BILLING_PROVIDERpolar (default)
POLAR_ACCESS_TOKENOrganization access token
POLAR_WEBHOOK_SECRETWebhook signing secret
POLAR_PRO_MONTHLY_PRODUCT_IDPro monthly product ID
POLAR_PRO_YEARLY_PRODUCT_IDPro yearly product ID (optional)
POLAR_SERVERsandbox or production

Ohne Polar-Env-Vars bleibt die App auf Free — kein Crash, Checkout deaktiviert.

Checkout & portal

ActionAPIUI
Upgrade to Procheckout(userId, "pro")Billing page → Upgrade
Manage subscriptionopenPortal(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 billingConfig

Das 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

  1. Setze POLAR_SERVER=sandbox und Sandbox-Token/Product-ID
  2. Nutze Polar-Testkarten im Checkout — siehe Polar sandbox docs
  3. Bestätige die Webhook-Zustellung im Polar-Dashboard (lokal ggf. ngrok)
  4. 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

WhatWhere
Public billing APIsrc/lib/billing/index.ts
Plans & feature gatessrc/lib/billing/config.ts
Polar providersrc/lib/billing/providers/polar/
Webhook handlersrc/lib/billing/providers/polar/webhooks.ts
Billing UI & actionssrc/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.