billing
billing 범주의 API 작업 6개를 안내합니다.
| Method | Path | Description |
|---|---|---|
| POST | /v1/hooks/polar | Polar 결제 웹훅 (docs/billing.md) |
| GET | /v1/orgs/{orgSlug}/billing | 조직의 요금제와 결제 상태 (docs/billing.md) |
| POST | /v1/orgs/{orgSlug}/billing/checkout | 프로 요금제 체크아웃 세션을 만든다 |
| POST | /v1/orgs/{orgSlug}/billing/portal | 고객 포털 세션을 만든다 (결제 수단·영수증·해지) |
| POST | /v1/orgs/{orgSlug}/billing/session | 고객 세션 토큰 (결제 수단 임베드의 인증) |
| POST | /v1/orgs/{orgSlug}/billing/cancel | 기간 끝 해지를 예약하거나 되돌린다 |
POST /v1/hooks/polar
사용자가 부르는 경로가 아니다. Polar 가 구독 사건을 여기에 배달한다. 인증은 토큰이 아니라 Standard Webhooks 서명이다 — webhook-id· webhook-timestamp·webhook-signature 셋과 서버의 RUNLOT_POLAR_WEBHOOK_SECRET 으로 검증한다 (아니면 403, 시각 ±5 분). 같은 webhook-id 는 한 번만 적용한다 (재전달은 200 이고 무시).
orgs.plan 을 바꾸는 유일한 자리다: subscription.active·updated 의 status active/trialing/past_due 는 pro, 그 밖(canceled·unpaid…)은 free. 서버에 토큰이 없으면 501. 클라이언트 라이브러리는 부르지 않는다.
operationId polarHook
Request body: application/json · object
| Status | Description | Response body |
|---|---|---|
| 200 | 받았다 (applied 가 참이면 요금제를 바꿨다) | — |
| 400 | — | — |
| 403 | 서명이 맞지 않는다 | — |
| 501 | 결제가 꺼져 있다 | — |
GET /v1/orgs/{orgSlug}/billing
요금제(plan)와, 프로면 그 상태(status: active · past_due · canceled)와 현재 기간의 끝(periodEnd)을 준다. enabled 가 거짓이면 이 배치에 결제가 설정되지 않은 것이다 — 화면은 문의 주소만 둔다. sandbox 가 참이면 Polar 의 테스트 환경이라 실제 청구가 없다.
limits 는 두 요금제의 한도 표다 (cp/internal/plan). 화면에 숫자를 두지 않는 이유는 OrgMembership.projectLimit 과 같다. viewer 이상.
operationId getBilling
| Status | Description | Response body |
|---|---|---|
| 200 | 결제 상태 | BillingStatus |
| 403 | — | — |
| 404 | — | — |
POST /v1/orgs/{orgSlug}/billing/checkout
Polar 체크아웃 세션 하나를 만들고 그 url 을 준다. 대시보드는 그 URL 을 자기 페이지 위의 iframe 으로 띄운다 (@polar-sh/checkout; 세션의 embed_origin 은 returnTo 의 origin). 결제가 끝나도 그것이 요금제를 바꾸지는 않는다: 요금제는 웹훅(/v1/hooks/polar)이 바꾼다. 화면은 성공 사건 뒤 상태를 다시 읽는다.
returnTo 는 서버의 RUNLOT_PUBLIC_URL 아래여야 한다 (아니면 400). 이미 프로면 409 already_pro — 포털을 열어야 한다. admin.
operationId createBillingCheckout
Request body: application/json · object
| Status | Description | Response body |
|---|---|---|
| 200 | 체크아웃 세션 | object |
| 400 | — | — |
| 403 | — | — |
| 404 | — | — |
| 409 | already_pro | Error |
| 501 | billing_off — 결제가 설정되지 않았다 | — |
| 502 | billing_unavailable — Polar 가 응답하지 않는다 | — |
POST /v1/orgs/{orgSlug}/billing/portal
Polar 고객 포털의 일회용 url 을 준다. 결제 수단 변경·영수증·해지는 전부 거기서 한다 — 우리 화면에 그 폼을 두지 않는다 (카드 번호가 우리 서버를 지나지 않는 것이 MoR 의 값이다). 한 번도 결제한 적 없는 org 는
404. admin.
operationId createBillingPortal
Request body: application/json · object
| Status | Description | Response body |
|---|---|---|
| 200 | 포털 세션 | object |
| 400 | — | — |
| 403 | — | — |
| 404 | — | — |
| 501 | billing_off | — |
| 502 | billing_unavailable | — |
POST /v1/orgs/{orgSlug}/billing/session
@polar-sh/checkout 의 결제 수단 임베드가 요구하는 고객 세션 토큰. 1 시간, 그 고객 하나. 화면이 열릴 때마다 새로 받는다. 결제 이력 없는 org 는 404. admin.
operationId createBillingSession
| Status | Description | Response body |
|---|---|---|
| 200 | 고객 세션 | object |
| 403 | — | — |
| 404 | — | — |
| 501 | billing_off | — |
| 502 | billing_unavailable | — |
POST /v1/orgs/{orgSlug}/billing/cancel
cancel: true 면 현재 기간이 끝나는 날 프로가 끝난다 (그날까지는 프로) — Polar 의 cancel_at_period_end. false 면 예약을 되돌린다. 즉시 회수는 없다: 낸 기간은 다 쓴다. 응답으로 우리 행의 status 를 바로 맞추고, 웹훅도 곧 같은 결론으로 온다. 프로가 아니면 409 not_pro. admin.
operationId cancelBilling
Request body: application/json · object
| Status | Description | Response body |
|---|---|---|
| 200 | The updated status | object |
| 403 | — | — |
| 404 | — | — |
| 409 | not_pro | Error |
| 501 | billing_off | — |
| 502 | billing_unavailable | — |