Plan check
if plan === pro 같은 조건으로 기능을 연다.
초기에는 빠르지만 pricing 변경과 예외 계약에 약하다.분류: Layer 13 - Product Engineering & Growth Systems
Product Engineer가 결제 기능을 다룬다는 것은 checkout 버튼을 붙이는 것에서 끝나지 않는다. 사용자가 어떤 plan에 있고, 결제가 실패했을 때 어떤 유예 상태가 되며, 어떤 feature에 접근할 수 있고, webhook이 지연되거나 중복되어도 제품 상태가 맞게 회복되는지를 설계해야 한다.
Billing, Subscription & Entitlement는 가격제, 구독 lifecycle, 결제 이벤트, 기능 접근 권한을 제품 도메인 모델과 연결하는 Product Engineering 역량이다.
SaaS 제품에서 billing은 revenue와 user experience가 만나는 가장 민감한 흐름이다.
Product Engineer는 결제 provider의 객체와 내부 제품 객체를 분리해서 이해해야 한다. Provider의 subscription과 내부의 workspace entitlement는 같지 않다. 하나는 billing source of truth이고, 다른 하나는 제품 접근 제어의 source of truth다.
초기 제품은 plan = pro이면 특정 기능을 열어주는 방식으로 충분해 보인다. 하지만 pricing이 바뀌고, add-on이 생기고, enterprise 예외 계약이 생기고, trial과 grace period가 생기면 plan 이름으로 기능 접근을 직접 판단하는 코드는 빠르게 복잡해진다.
Stripe의 Billing Entitlements 문서는 Stripe product와 내부 service feature를 매핑하고 entitlement 변경 webhook으로 access provisioning을 구동하는 구조를 제안한다. Subscription Webhooks 문서는 subscription status change, payment failure, cancellation 같은 lifecycle event를 webhook으로 처리해야 함을 보여준다.
따라서 entitlement는 “plan 이름을 feature check로 바로 쓰는 방식”의 한계에서 나온다. Product Engineer는 결제 상태와 기능 접근 권한 사이에 entitlement layer를 두어 pricing 변화, webhook 지연, 예외 계약, rollout을 견딜 수 있게 만들어야 한다.
| 객체 | 의미 | 내부 모델링 포인트 |
|---|---|---|
| Customer | 결제 provider의 고객 | 내부 workspace/account와 매핑 |
| Product/Price | 판매 상품과 가격 | 내부 plan 또는 package와 매핑 |
| Subscription | 반복 결제 상태 | trialing, active, past_due, canceled |
| Invoice | 청구 단위 | payment_failed, paid, void |
| Payment Method | 결제 수단 | 만료, 실패, 갱신 필요 |
| Entitlement | 기능 접근 권한 | feature key, limit, enabled |
| Seat/Usage | 사용량 기반 과금 축 | member count, API calls, storage |
중요한 것은 결제 provider 객체를 내부 제품 모델에 그대로 복사하지 않는 것이다. 내부 제품은 workspace_id, entitlement_key, feature_limit, access_state 같은 자체 언어가 필요하다.
subscription은 단순히 active/inactive가 아니다.
trialing -> activetrialing -> canceledactive -> past_duepast_due -> activepast_due -> canceledactive -> canceled_at_period_endcanceled_at_period_end -> canceled각 상태는 제품 접근에 영향을 준다.
| 상태 | 제품 접근 판단 |
|---|---|
| trialing | trial entitlement 열림, trial end 안내 |
| active | paid entitlement 열림 |
| past_due | 유예 기간 동안 제한적 접근 또는 안내 |
| canceled_at_period_end | 기간 종료 전까지 접근 유지 |
| canceled | paid entitlement 회수 |
Product Engineer는 이 표를 제품 정책으로 명확히 해야 한다. 결제 provider의 상태를 그대로 노출하는 것보다, 내부 access state로 번역하는 편이 안전하다.
퀴즈
힌트: pricing은 바뀌지만 기능 접근 규칙은 더 안정적인 계약이어야 한다.
plan 변경, add-on, enterprise 예외, trial, grace period가 생기면 plan 조건문이 코드 곳곳에 퍼진다. entitlement를 별도 모델로 두면 결제 상품과 내부 기능 접근 권한을 느슨하게 연결할 수 있다.
Entitlement는 “이 account가 이 feature를 어떤 한도로 사용할 수 있는가”를 표현한다.
type Entitlement = { workspaceId: string; featureKey: "advanced_export" | "sso" | "api_access"; enabled: boolean; limit?: number; source: "subscription" | "trial" | "enterprise_override"; validUntil?: string;};이 모델을 두면 UI, API, background job이 같은 방식으로 feature access를 확인할 수 있다.
if plan === pro 같은 조건으로 기능을 연다.
초기에는 빠르지만 pricing 변경과 예외 계약에 약하다.feature key별 접근 권한과 limit을 조회한다.
add-on, trial, enterprise override, staged rollout에 강하다.entitlement의 limit과 현재 사용량을 함께 본다.
seat, API call, storage, export count 같은 제한에 필요하다.누가 언제 entitlement를 열고 닫았는지 추적한다.
billing incident와 support 대응에 필요하다.결제 상태는 webhook으로 들어오는 경우가 많다. webhook은 반드시 중복, 역순, 지연을 견딘다고 가정해야 한다.
필수 기준은 다음과 같다.
시나리오
provider에는 subscription이 active로 보이지만 내부 workspace entitlement는 free 상태다. 사용자는 결제 완료 직후 premium export가 막혔다고 문의했다.
webhook event log, subscription mapping, entitlement provisioning, retry/reconcile 중 무엇을 어떤 순서로 확인할 것인가?Billing은 시스템 상태이면서 UX다. 사용자는 돈과 접근 권한에 민감하므로 상태를 명확히 보여줘야 한다.
| 상황 | UX 원칙 |
|---|---|
| trial 종료 임박 | 남은 기간과 다음 과금 시점을 명확히 보여준다 |
| payment failed | 기능 차단보다 먼저 복구 행동을 제공한다 |
| seat limit | 왜 막혔고 어떤 선택지가 있는지 보여준다 |
| cancellation | 즉시 해지와 period end 해지를 구분한다 |
| upgrade success | 어떤 기능이 열렸는지 즉시 확인시킨다 |
| downgrade | 잃는 기능과 데이터 보존 정책을 설명한다 |
Product Engineer는 billing state와 UI state를 맞춰야 한다. 내부 상태는 active인데 UI가 past_due로 보이거나, entitlement가 닫혔는데 UI가 upgrade success를 보여주면 신뢰가 깨진다.
Billing 기능도 제품 지표로 봐야 한다.
하지만 결제 이벤트에는 민감정보가 많다. card number, email 원문, 주소, invoice 상세 원문을 analytics event에 넣지 않는다. 필요한 경우 결제 provider id와 내부 id도 접근 제한을 둔다.
Billing은 제품 도메인 모델과 privacy governance를 동시에 요구한다.
Frontend Product Quality & RUM: 결제와 권한 흐름의 오류·성능·폼 안정성을 본다.Privacy, Consent & Product Data Governance: 결제 정보와 분석 이벤트의 개인정보 경계를 다룬다.Product Domain Modeling: workspace, membership, entitlement의 상태 전이를 기반으로 한다.