Polar.sh — Hướng dẫn cấu hình (Org Access Token + Webhook + Super-admin)
Hướng dẫn end-to-end để bật Polar.sh làm billing provider cho subscription. Làm sandbox trước, verify xong mới mirror sang production.
Liên quan:
../../architecture/polar-integration-plan.md·README.md
0. Sandbox vs Production
Polar tách hẳn 2 môi trường, dữ liệu + token + secret KHÔNG dùng chung:
| Môi trường | Dashboard | API server (SDK) |
|---|---|---|
| Sandbox (test) | https://sandbox.polar.sh |
https://sandbox-api.polar.sh |
| Production | https://polar.sh |
https://api.polar.sh |
Trong super-admin, toggle Sandbox / test mode quyết định SDK nối server nào
(isTest=true → server: 'sandbox'). Sandbox dashboard có banner vàng:
"Changes you make here don't affect your live account · Payments are not processed".
⚠️ Token tạo ở sandbox chỉ chạy với
server: 'sandbox'. Bật nhầm toggle = Health check 401.
1. Organization Access Token
Token này để backend gọi Polar API (tạo checkout, cancel sub, health check…).
Polar đã bỏ Personal Access Token. Dùng Organization Access Token (scope theo 1 org, nên
organization_idđược phép bỏ qua trong API call).
Đường đi
Có 2 lối tới cùng một panel "Create API key":
- Cách A — Org Settings (chính):
https://<sandbox|polar>.polar.sh/dashboard/<org-slug>/settings→ cuộn xuống mục Developers ("Manage access tokens to authenticate with the Polar API") → Create token. - Cách B — Onboarding: màn Account Review → Checkout integration → "Create an API key" (Required) cũng mở đúng panel này.
❌ KHÔNG dùng
User Settings → Developer— đó là cấp account, chứa OAuth Applications + báo "Access tokens have moved → use Organization access tokens". Token mình cần là Organization Access Token (cấp org). ❌ KHÔNG bấm New OAuth App (dành cho OAuth flow của app bên thứ 3, không phải API token).
Điền panel "Create API key"
| Field | Giá trị |
|---|---|
| Name | Glamvoo Booking API (chỉ để nhận diện) |
| Expiration | No expiration (hoặc dài nhất) ❗ — mặc định panel là 30 days; để vậy thì token hết hạn → checkout/health chết. Nếu buộc có hạn, đặt lịch rotate. |
| Scopes | tick đúng 5 scope dưới. Sandbox có thể Select all; production NÊN chỉ tick 5 cái (ít quyền nhất). |
Scopes cần (khớp các call trong polar.adapter.ts):
| Scope | Dùng cho |
|---|---|
products:read |
Health check (products.list) |
checkouts:write |
Tạo checkout (checkouts.create) |
subscriptions:read |
Lấy sub cho customer portal |
subscriptions:write |
Cancel / đổi seats |
customer_sessions:write |
Tạo customer portal session |
Lấy giá trị
- Bấm tạo → Polar hiện token một lần duy nhất (dạng
polar_oat_...). - Copy ngay, lưu password manager. Mất thì phải tạo token mới.
- 👉 Sẽ paste vào field API key ở bước 3. Không paste vào chat/commit.
2. Webhook + Signing Secret
Webhook để Polar đẩy event (subscription created/updated/canceled, order paid…) về API mình; signing secret để verify chữ ký (Standard Webhooks).
Đường đi
Settings (org) → Webhooks (nav trái) → Add Endpoint / Create webhook.
Điền form
| Field | Giá trị |
|---|---|
| Name | tùy chọn (vd Glamvoo Booking) |
| URL | https://app-dev.novagoo.com/api/webhooks/subscription/polar (sandbox/dev) |
| Format | dropdown "Select a payload format" → Raw ❗ (Standard Webhooks — KHÔNG chọn Discord/Slack) |
| Secret | để Polar tự generate |
URL = tunnel HTTPS trỏ về API local (Bambora/Polar callback dùng chung). Xem
../caddy-dev-setup.md. Production = domain API thậthttps://<prod>/api/webhooks/subscription/polar(KHÔNG phảiapp-dev).Route
POST /webhooks/subscription/:providernhận mọiBillingProviderenum, nên…/polarhoạt động ngay khi enum cóPOLAR(đã có).
Events cần tick
Màn "Create webhook" liệt kê event theo nhóm (Benefit, Customer, Order, Product…). Cuộn xuống tìm nhóm Subscription + Order, tick:
Subscription: subscription.created, subscription.updated,
subscription.active, subscription.canceled, subscription.uncanceled,
subscription.revoked, subscription.past_due
Order: order.created, order.paid, order.refunded
order.updatedkhông cần (mình không map — chỉ dùng created/paid cho renewal, refunded cho refund). Tick thừa cũng vô hại (event không map → ghi inbox rồi bỏ qua), nhưng để gọn thì bỏ.Bỏ qua hẳn các nhóm Benefit / Benefit Grant / Checkout / Customer / Customer Seat / Member / Organization / Product — không thuộc luồng subscription.
Lấy giá trị
- Mở lại endpoint vừa tạo → phần Signing Secret → Reveal/Show → copy.
- 👉 Sẽ paste vào field Webhook secret ở bước 3. Không paste vào chat.
Webhook chỉ "sống" khi tunnel
app-dev.novagoo.comđang chạy và API localyarn start:devđang bật. Lúc tạo endpoint thì chưa cần; lúc test checkout thật (P4) thì cả hai phải bật.
3. Cấu hình Super-admin → Health check → Activate
Vào /admin/superadmin/settings?tab=billing → Add billing provider.
Health check chạy theo config id cụ thể, độc lập trạng thái active — nên kiểm tra credentials TRƯỚC, xanh rồi mới Activate (Activate = go-live).
| Field | Giá trị |
|---|---|
| Sandbox / test mode | ✅ ON (sandbox) |
| Store ID | để trống (field LS-legacy; token đã org-scoped) |
| Display name | Polar Sandbox (tùy) |
| API base URL | để trống (Polar dùng toggle server, không dùng base URL) |
| Store domain | để trống (LS-legacy) |
| API key ★ | Organization Access Token (polar_oat_...) từ bước 1 |
| Webhook secret ★ | Signing secret từ bước 2 |
Sau đó:
- Save.
- Health check ngay trên dòng config vừa tạo → kỳ vọng ok / xanh
(backend gọi
products.listvới credentials của config đó). - Xanh rồi → Activate dòng Polar (chỉ 1 config active tại 1 thời điểm).
Troubleshooting Health check
| Triệu chứng | Nguyên nhân thường gặp |
|---|---|
| 401 / invalid token | Toggle sandbox/prod sai môi trường với token; hoặc copy thiếu ký tự |
| 403 / forbidden | Token thiếu scope products:read |
| "No active Polar config" | Chưa Activate, hoặc config active là provider khác |
4. Sau khi Health check xanh (các bước tiếp)
- Plan mappings (P4.5): trong config → Plan mappings → map
planKey = premium_1_month→ product id Polar.- Sandbox Premium product id:
850a5748-9270-44f3-88d2-ed076f3d8d20.
- Sandbox Premium product id:
- ⚠️ TẮT Free trial trên Polar product. Trial 14 ngày do hệ thống mình
quản (trial-on-signup) — bật trial trên Polar nữa sẽ double-trial + hoãn
charge 14 ngày. Polar chỉ charge khi owner subscribe. Xem
../../architecture/subscription-enforcement-plan.md. - Checkout E2E (P4): chạy thử checkout sandbox (cần tunnel + API live), đối
chiếu field name payload webhook thật với
polar.event-mapper.ts(với Polar trial OFF,status: activekhông có field trial →trialEndsAt: nullhiện tại trong mapper là đúng, không cần sửa).
5. Mirror sang Production (sau khi sandbox xong)
- Tạo Organization Access Token tại
https://polar.sh/dashboard/<org>/settings(org production, ví dụnovago). - Tạo webhook endpoint trỏ domain production, Format Raw, copy secret mới.
- Super-admin: tạo config mới với Sandbox / test mode = OFF, paste token + secret production → Activate → Health check.
- Plan mapping trỏ product id production (org
novago:6feccf1c-74e3-4cef-b76f-5d639079bab5).