Changelog — webspresso/plugins/polar
1.0.1 — Unreleased #
Fixed #
- package exports:
webspresso/core/auth,webspresso/plugins/polar, andwebspresso/plugins/polar/src/*resolve without manual/index.jssuffix - Rate limiting: Polar limiters use
express-rate-limitv8ipKeyGenerator(fixes production startupValidationError) - Webhook user lookup:
metadata.user_idsupports nanoid/UUID string primary keys - CSP:
polarCspDirectives()includes'self'informActionandconnectSrc
Added #
hooks.isPaidUser({ user, knex, config })for external tier tables (legacy(user, knex, config)still supported)syncBeforeCheckoutoption (defaulttrue)polar.api.syncBillingForAppUser(user, knex, { hooks })for dashboard page-load syncmergePolarCspDirectives(existing)for Helmet CSP union merge- Production checklist:
docs/guides/polar-production-checklist.md - Integration tests: rate-limit startup, IPv6 key validation,
isPaidUserhook
1.0.0 — 2026-09-10 #
First production-ready release.
Added #
- Zero-dependency Polar SDK (
crypto+fetch): webhook verification, API client, checkout, portal, Customer State sync - Auto routes: webhook, checkout (GET/POST), portal, billing status JSON API
- Idempotent migration generator +
webspresso polar:migrateCLI - Configurable field mapping, tier/plan mapping, hooks (
onSubscriptionChange) - Optional
polarSyncMiddlewarefor page-load tier refresh - Nunjucks
dateLabelfilter + Date-safetruncatewrapper - CSP helper (
polarCspDirectives) for checkout form redirects - Zod env schema export (
parseEnv,polarEnvSchema)
Production lessons encoded #
| Issue | Fix |
|---|---|
Checkout URL is often https://polar.sh/checkout/polar_cst_*, not only buy.polar.sh | extractCheckoutUrlFromResponse + client_secret builder |
https://polar.sh/purchases/subscriptions → 404 | Blocked in URL validators |
subscription.created with incomplete status | Fallback Customer State sync before granting Pro |
| Metadata may not propagate to subscription | external_customer_id required on checkout; multi-strategy user resolver |
polar_current_period_end as Date breaks Nunjucks truncate | Always persist ISO strings; dateLabel + safe truncate |
| Checkout "already subscribed" API error | Pre-checkout syncPolarBillingForUser + early redirect |
| Webhook 401 | Standard Webhooks HMAC (webhook-id, webhook-timestamp, whsec_ decode); document raw body requirement |
| Sandbox vs production OAT | POLAR_SANDBOX=true → sandbox-api.polar.sh |
| Empty metadata fields → Polar 422 | buildCheckoutMetadata omits blank values |
| Portal deprecated paths | Validate /{org}/portal only; fallback POST /v1/customer-sessions/ |
Migration notes for existing custom Polar integrations #
- Remove local
utils/polar.jsand file routes under billing/webhook paths — plugin registers equivalent routes - Run
webspresso polar:migrate(orplugin.api.generateMigration()) once - Mount
createWebhookRawBodyMiddleware('/api/v1/polar/webhook')beforeexpress.json()in custom servers for strict signature verification - Wire
hooks.onSubscriptionChangeif tier updates need side effects (limits, analytics, etc.)