Documentación
Pagos (Polar)
Facturación Polar en Motoko Base — configuración, checkout, portal, webhooks y entitlements.
Motoko Base usa Polar para suscripciones en v1. La facturación está envuelta en una API agnóstica al proveedor — el código de feature nunca importa Polar directamente.
Official docs: Polar documentation
Architecture
Polar (source of truth)
↓
polar.customers.getStateExternal(userId)
↓
BillingSummary (plan, status, period end)
↓
Dashboard UI + hasPlan() / canAccess()Polar es la fuente de verdad. Motoko Base no refleja las suscripciones en Postgres. El estado del plan se obtiene del Polar Customer State y se cachea brevemente (60s). Los webhooks invalidan la caché cuando cambian las suscripciones.
Better Auth user id = Polar externalId. Se crea un cliente Polar en el sign-up cuando la facturación está configurada.
Setup
- Crea una organización Polar — empieza en sandbox
- Crea un producto de suscripción Pro → copia el product ID
- Crea un Organization Access Token
- Añade un webhook →
{APP_URL}/api/billing/webhooks/polar - Define las env vars en
.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 |
Sin las env vars de Polar la app permanece en Free — sin crash, checkout deshabilitado.
Checkout & portal
| Action | API | UI |
|---|---|---|
| Upgrade to Pro | checkout(userId, "pro") | Billing page → Upgrade |
| Manage subscription | openPortal(userId) | Billing page → Manage billing |
Checkout redirige a Polar Checkout. El portal abre Polar Customer Portal (cancelar, actualizar método de pago, facturas).
Webhooks
Ruta: src/app/api/billing/webhooks/polar/
Polar envía eventos de suscripción → se verifica la firma → se invalida la caché de billing → se captura un evento de analytics. Sin ledger local de suscripciones.
Suscríbete a los eventos de suscripción + customer.state_changed en el dashboard de Polar. Los tipos de evento no manejados se ignoran de forma segura.
Entitlements
import { hasPlan, canAccess } from "@/lib/billing";
await hasPlan(userId, "pro");
await canAccess(userId, "ai"); // feature → plan map in billingConfigEl mapeo feature→plan vive en src/lib/billing/config.ts. Las features demo no están gated en v1 — llama a canAccess cuando añadas gates de pago.
Etiquetas de plan en la UI: src/lib/billing/config/plans.ts.
Sandbox testing
- Define
POLAR_SERVER=sandboxy el token/product ID de sandbox - Usa las tarjetas de prueba de Polar en checkout — consulta Polar sandbox docs
- Confirma la entrega de webhooks en el dashboard de Polar (usa ngrok en local si hace falta)
- Tras el checkout, abre
/dashboard/settings/administration/billing?checkout=success— el plan debería actualizarse
Cambia a POLAR_SERVER=production con credenciales de producción para facturación en vivo.
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-* |
No importes @polar-sh/* desde features o UI — usa solo @/lib/billing.
Environment Variables — todas las env vars de Polar.