업그레이드와 애드온을 위한 요금제 및 권한 데이터베이스 스키마
업그레이드, 애드온, 트라이얼 및 철회를 하드코딩 없이 지원하는 요금제 및 권한 데이터베이스 스키마. 명확한 테이블과 시간 기반 검사로 접근을 통일합니다.

요금제와 기능이 빠르게 복잡해지는 이유
요금제는 가격 페이지에서는 단순해 보입니다: Basic, Pro, Enterprise. 문제는 이런 이름을 실제 앱의 접근 규칙으로 바꾸려 할 때 시작됩니다.
처음에는 기능 검사를 하드코딩하는 것(예: if plan = Pro then allow X)이 통합니다. 그러다가 요금제가 바뀌고, 어떤 기능이 Pro에서 Basic으로 이동하거나 새 애드온이 생기거나 영업에서 맞춤 번들을 주기도 합니다. 그러면 동일한 규칙이 API, UI, 모바일 앱, 백그라운드 작업에 복사되어 놓이게 됩니다. 한 곳만 바꾸고 다른 곳을 잊으면 사용자가 눈치챕니다.
두 번째 문제는 시간입니다. 구독은 정적인 라벨이 아니며 사이클 중간에 바뀝니다. 누군가는 오늘 업그레이드하고 다음 달에 다운그레이드하거나 일시 중지하거나 남은 유료 기간을 두고 해지합니다. 데이터베이스에 "현재 플랜"만 저장하면 타임라인을 잃어버려 나중에 기본 질문에 답할 수 없습니다: 지난 화요일에 어떤 접근 권한이 있었나? 왜 지원팀이 환불을 승인했나?
애드온은 요금제를 가로질러 작동하기 때문에 상황을 더 복잡하게 만듭니다. 애드온은 추가 좌석을 열어주거나 제한을 제거하거나 특정 기능을 활성화할 수 있습니다. 사람들은 어떤 플랜에서든 애드온을 구매하고 나중에 제거하거나 다운그레이드 후에도 유지할 수 있습니다. 규칙이 코드 안에 박혀 있다면 특수 케이스가 점점 쌓입니다.
일반적으로 나쁜 설계를 깨뜨리는 상황은 다음과 같습니다:
- 사이클 중간 업그레이드: 접근권은 즉시 바뀌어야 하며 청구 정산(proration)은 다른 규칙을 따를 수 있음.
- 예정된 다운그레이드: 유료 기간이 끝날 때까지 높은 접근권을 유지할 수 있음.
- 그랜파더링: 기존 고객이 신규 고객에게 없는 기능을 유지함.
- 맞춤 거래: 같은 플랜 이름을 쓰더라도 어떤 계정은 기능 A만 있고 B는 없음.
- 감사 요구: 지원/재무/컴플라이언스가 "정확히 언제 무엇이 활성화되었나?"를 묻는 경우.
목표는 간단합니다: 가격 변경에 따라 유연하게 변하는 접근 제어 모델. 매번 비즈니스 로직을 다시 쓰지 않고 한 곳에서 "그들이 이걸 할 수 있나?"를 물어보고, 데이터베이스에 그 이유를 설명하는 타임라인을 남기는 것.
이 글을 읽고 나면 복사해서 쓸 수 있는 스키마 패턴을 갖게 됩니다: 요금제와 애드온은 입력이 되고, 권한(entitlements)이 기능 접근의 단일 출처가 됩니다. 같은 접근법은 AppMaster 같은 노코드 빌더에도 잘 맞습니다. 규칙을 데이터로 유지하고 백엔드, 웹, 모바일에서 일관되게 조회할 수 있기 때문입니다.
핵심 용어: 플랜, 애드온, 권한(entitlement), 접근
많은 구독 문제는 어휘의 문제에서 시작합니다. 같은 단어를 서로 다르게 쓰면 스키마는 특수 케이스로 변합니다.
요금제와 권한 스키마에서 분리해 둘 가치가 있는 용어는 다음과 같습니다:
- Plan(플랜): 구독 시 기본으로 받는 번들(예: Basic 또는 Pro). 보통 기본 한도와 포함된 기능을 설정합니다.
- Add-on(애드온): 기본을 변경하는 선택적 구매(예: "추가 좌석" 또는 "고급 리포팅"). 애드온은 플랜을 바꾸지 않고도 붙였다 뗄 수 있어야 합니다.
- Entitlement(권한): 플랜 + 애드온 + 오버라이드를 결합한 후의 최종 계산된 "지금 그들이 가진 것". 앱은 이것을 조회해야 합니다.
- Permission(권한 또는 기능): 누군가가 할 수 있는 특정 행동(예: "데이터 내보내기" 또는 "청구 관리"). 권한은 역할과 entitlement에 따라 달라집니다.
- Access(접근): 앱이 규칙을 적용했을 때의 실제 결과(화면이 기능을 보이거나 숨김, API 호출 허용 또는 차단, 한도 적용).
기능 플래그는 관련 있지만 다릅니다. **기능 플래그(feature flag)**는 보통 롤아웃, 실험, 사고 시 기능 끄기 등 제품 수준의 스위치입니다. **Entitlement(권한)**은 고객별 접근으로, 결제나 계약에 따라 달라집니다. 그룹 단위 행동 변경이 필요하면 플래그를 쓰고, 접근이 결제와 일치해야 하면 엔타이틀먼트를 사용하세요.
범위도 혼란의 원인입니다. 다음을 분명히 하세요:
- User(사용자): 한 사람. 역할(관리자 대 일반)과 개인 한도에 적합합니다.
- Account(계정, 고객): 결제 주체. 청구 정보와 구독 소유권에 적합합니다.
- Workspace(작업 공간/팀): 실제 작업이 일어나는 곳. 많은 제품은 여기에서 entitlement(좌석, 저장공간, 활성화된 모듈)를 적용합니다.
시간은 중요하므로 직접 모델링하세요:
- Start와 End: 엔타이틀먼트는 특정 기간에만 활성일 수 있음(트라이얼, 프로모션, 연간 계약).
- 예정된 변경: 업그레이드는 즉시 시작될 수 있고, 다운그레이드는 갱신 시점에 시작되는 경우가 많음.
- 유예 및 취소: 결제 실패 후 제한된 접근을 허용하되 명확한 종료일을 둠.
예: 한 회사가 Pro를 사용 중에 중간에 "고급 리포팅" 애드온을 추가하고 다음 사이클에 Basic으로 다운그레이드를 예약했다고 합시다. 플랜은 추후 바뀌고 애드온은 지금 시작됩니다. 엔타이틀먼트 계층은 "오늘 이 워크스페이스가 고급 리포트를 사용할 수 있나?"를 묻는 단일한 장소로 남습니다.
플랜과 기능을 위한 간단한 핵심 스키마
좋은 요금제 및 권한 데이터베이스 스키마는 작게 시작합니다: 판매하는 것(플랜과 애드온)과 사람들이 실제로 할 수 있는 것(기능)을 분리하세요. 이 둘을 분명히 하면 업그레이드나 새 애드온이 코드 변경이 아니라 데이터 변경으로 끝납니다.
다음은 대부분의 구독 제품에 유용한 실용적인 핵심 테이블 세트입니다:
- products: 판매 가능한 항목(기본 플랜, 팀 플랜, 추가 좌석 애드온, 우선 지원 애드온).
- plans: 플랜을 제품의 특수형으로 다루고 싶다면 사용(청구 주기, 공개 표시 순서 등). 많은 팀은
products안에 플랜을 넣고product_type컬럼을 사용합니다. - features: 기능 카탈로그(API 접근, 최대 프로젝트 수, 내보내기, SSO, SMS 크레딧 등).
- product_features(또는
plan_features): 어떤 제품에 어떤 기능이 들어가는지 값을 포함해 연결하는 조인 테이블.
이 조인 테이블이 대부분의 힘이 있는 곳입니다. 기능은 단순한 켜기/끄기가 아닙니다. 플랜은 max_projects = 10을 줄 수 있고, 애드온은 +5를 더할 수 있습니다. 그래서 product_features는 최소한 다음을 지원해야 합니다:
feature_value(숫자, 텍스트, JSON 또는 별도 컬럼)value_type(boolean, integer, enum, json)grant_mode(교체(replace) 대 추가(add)), 애드온이 기본 한도를 덮어쓰지 않고 "좌석 +5"처럼 동작하게 하기 위함
애드온도 제품으로 모델링하세요. 차이는 구매 방식뿐입니다. 기본 플랜 제품은 "한 번에 하나"이고, 애드온 제품은 수량을 허용할 수 있습니다. 하지만 둘 다 기능으로 매핑되는 방식은 동일합니다. 이렇게 하면 "애드온 X면 기능 Y 활성화" 같은 특수 케이스가 코드에 흩어지는 것을 피할 수 있습니다.
기능은 데이터로 유지해야 합니다. 여러 서비스에 기능 검사를 하드코딩하면 결국 불일치가 발생합니다(웹은 예스, 모바일은 아니고 백엔드는 다름). 기능이 데이터베이스에 있으면 앱은 일관되게 한 가지 질문을 할 수 있고, 행을 편집해 변경을 배포할 수 있습니다.
이름 짓기는 사람들이 생각보다 더 중요하게 여기지 않습니다. 마케팅 이름이 바뀌더라도 절대 바꾸지 않을 안정적인 식별자를 사용하세요:
feature_key예:max_projects,sso,priority_supportproduct_code예:plan_starter_monthly,addon_extra_seats
표시 라벨은 별도로 보관하세요(feature_name, product_name). AppMaster의 Data Designer와 PostgreSQL을 사용한다면 이러한 키를 고유 필드로 취급하는 것이 곧바로 효과를 발휘합니다: 통합과 리포팅을 안정적으로 유지하면서 안전하게 재생성할 수 있습니다.
권한 계층: "할 수 있나?"를 묻는 한 곳
대부분의 구독 시스템은 "무엇을 샀는가"를 한 곳에 저장하지만 "무엇을 할 수 있는가"를 다섯 군데에서 계산할 때 꼬입니다. 해결책은 권한 계층(entitlement layer): 특정 시점의 주체(subject)에 대한 실효적 접근을 나타내는 단일 테이블(또는 뷰)입니다.
업그레이드, 다운그레이드, 트라이얼, 일회성 부여를 견디는 스키마를 목표로 한다면 이 계층이 모든 것을 예측 가능하게 만드는 부분입니다.
실용적인 entitlements 테이블
각 행을 한 개의 클레임으로 생각하세요: "이 주체는 이 기능에 대해 이 값을 이 기간 동안 이 출처로 갖는다." 일반적인 형태는 다음과 같습니다:
- subject_type(예: "account", "user", "org") 및 subject_id
- feature_id
- value(그 기능의 실효 값)
- source(어디서 왔는지: "direct", "plan", "addon", "default")
- starts_at 및 ends_at(끝날 때까지의 nullable ends_at은 지속 접근을 의미)
값은 여러 방식으로 구현할 수 있습니다: 단일 text/JSON 컬럼과 value_type, 또는 value_bool, value_int, value_text 같은 별도 컬럼. 단순하고 쿼리하기 편하게 유지하세요.
대부분의 제품을 커버하는 값 타입
기능은 항상 켜기/끄기가 아닙니다. 다음 값 타입이 실제 청구와 접근 제어 요구를 충분히 커버합니다:
- Boolean: 활성/비활성(예:
can_export = true) - Quota number: 한도(예:
seats = 10,api_calls = 100000) - Tier level: 등급(예:
support_tier = 2) - String: 모드나 변형(예:
data_retention = "90_days")
우선순위: 충돌을 어떻게 해결할지
충돌은 정상입니다. 사용자가 플랜으로 5좌석을 받고, 애드온으로 10좌석을 더 받고, 지원으로 수동 부여를 받을 수 있습니다.
명확한 규칙을 정하고 모든 곳에서 일관되게 적용하세요:
- 직접 부여(direct grant)가 플랜을 덮음
- 그 다음 애드온
- 그 다음 기본값
간단한 접근은 후보 행 모두(플랜 유래, 애드온 유래, 직접)를 저장하고 source 우선순위와 최신 starts_at으로 정렬해 주체+기능별로 최종 "승자"를 계산하는 것입니다.
구체적 시나리오: 고객이 오늘 플랜을 다운그레이드했지만 월말까지 유효한 애드온을 이미 결제했다면 starts_at/ends_at이 있는 엔타이틀먼트로 플랜 기반 기능은 즉시 변경되고 애드온 기능은 ends_at까지 유지됩니다. 앱은 특수 케이스 없이 한 번의 쿼리로 "할 수 있나?"에 답할 수 있습니다.
구독, 아이템, 시간 제한 접근
플랜 카탈로그(플랜, 애드온, 기능)는 "무엇을 파는가"입니다. 구독은 "누가 무엇을 언제 갖는가"입니다. 이들을 분리하면 업그레이드와 취소가 덜 두려워집니다.
실용적 패턴은: 계정당 한 개의 구독(subscription)과 그 아래 여러 구독 아이템(subscription_items)을 두는 것입니다(기본 플랜 하나와 0개 이상 애드온). 이렇게 하면 타임라인을 깔끔하게 기록하면서 접근 규칙을 다시 쓰지 않아도 됩니다.
구매 타임라인을 모델링하기 위한 핵심 테이블
다음 두 테이블로 간단하게 유지할 수 있습니다:
- subscriptions: id, account_id, status(active, trialing, canceled, past_due), started_at, current_period_start, current_period_end, canceled_at(nullable)
- subscription_items: id, subscription_id, item_type(plan, addon), plan_id/addon_id, quantity, started_at, ends_at(nullable), source(stripe, manual, promo)
일반적인 세부사항: 각 아이템을 자체 날짜로 저장하세요. 이렇게 하면 애드온을 30일만 부여하거나 고객이 해지해도 기존 기간은 유지하게 할 수 있습니다.
정산과 청구를 접근 로직에서 분리하세요
정산(proration), 청구서, 결제 재시도는 청구 문제입니다. 기능 접근은 권한 문제입니다. 인보이스 라인에서 "접근을 계산"하려 하지 마세요.
대신 청구 이벤트가 구독 레코드를 업데이트하도록 하세요(예: current_period_end 연장, 새로운 subscription_item 행 생성, ends_at 설정). 앱은 청구 수학이 아니라 구독 타임라인(그리고 나중에는 엔타이틀먼트 계층)에서 접근 질문에 답합니다.
놀라움 없는 예정된 변경
업그레이드와 다운그레이드는 종종 특정 시점에 적용됩니다:
- 구독에 pending_plan_id와 change_at을 추가해 단일 예정 플랜 변경을 처리하세요.
- 또는 여러 미래 변경을 관리하려면 subscription_changes 테이블(subscription_id, effective_at, from_plan_id, to_plan_id, reason)을 사용하세요.
이렇게 하면 "다운그레이드는 기간 종료에 일어난다" 같은 규칙이 코드 곳곳에 하드코딩되는 것을 막습니다. 일정 자체가 데이터입니다.
트라이얼은 어디에 속하나
트라이얼은 단지 다른 출처의 시간 제한 접근입니다. 두 가지 깔끔한 옵션:
- 트라이얼을 구독 상태(trialing)로 처리하고 trial_start/trial_end 날짜를 둔다.
- 또는 시작/종료 날짜와 source = trial인 트라이얼 아이템/엔타이틀먼트를 생성한다.
AppMaster로 빌드한다면 이 테이블들은 Data Designer에 깔끔하게 매핑되며, 날짜로 "지금 활성인지" 쿼리하기 쉬워 특수 케이스가 줄어듭니다.
단계별: 패턴 구현
좋은 요금제 및 권한 스키마는 하나의 약속으로 시작합니다: 기능 로직은 퍼져 있는 코드가 아니라 데이터에 있어야 합니다. 앱은 단 하나의 질문, "지금의 실효 권한은 무엇인가?"를 물어보고 명확한 답을 받아야 합니다.
1) 안정적인 키로 기능 정의하기
변경되지 않을 안정적인, 사람이 읽을 수 있는 키를 가진 feature 테이블을 만드세요(마케팅 라벨이 바뀌어도 절대 이름을 바꾸지 않을 것). 좋은 키 예: export_csv, api_calls_per_month, seats.
값을 처리할 방법(불리언 대 숫자 등)도 추가하세요. 단순하고 일관되게 유지합니다.
2) 플랜과 애드온을 엔타이틀먼트에 매핑하기
이제 두 가지 진실의 원천이 필요합니다: 플랜이 무엇을 포함하는지, 각 애드온이 무엇을 부여하는지.
실용적인 순서는 다음과 같습니다:
- 모든 기능을 안정적인 키와 값 타입이 있는 하나의
feature테이블에 넣습니다. - 각 플랜이 기능 값을 부여하는
plan과plan_entitlement를 만듭니다(예:seats = 5,export_csv = true). - 각 애드온이 추가 값을 부여하는
addon과addon_entitlement를 만듭니다(예:seats + 10,api_calls_per_month + 50000,priority_support = true). - 값 결합 방식을 결정합니다: 불리언은 보통 OR, 숫자 한도는 보통 MAX(큰 값 우선), 좌석처럼 합쳐야 하는 항목은 SUM.
- 엔타이틀먼트가 시작/종료되는 시점을 기록해 업그레이드, 취소, 정산이 접근 검사에 문제를 일으키지 않게 합니다.
AppMaster로 구축하면 Data Designer에 이 테이블들을 모델링하고 결합 규칙은 Business Process 로직에서 작은 정책 테이블 또는 enum으로 관리하는 것이 좋습니다.
3) "실효 권한" 생성하기
두 가지 옵션이 있습니다: 읽을 때 계산하거나(읽기 시 계산), 변경이 있을 때 스냅샷을 생성(캐시)하는 방법. 대부분의 앱에서는 스냅샷이 이해하기 쉽고 부하 시 빠릅니다.
일반적인 접근은 account_entitlement 테이블에 기능별 최종 결과와 valid_from, valid_to를 저장하는 것입니다.
4) 하나의 검사로 접근 강제화하기
화면, 엔드포인트, 백그라운드 작업 곳곳에 규칙을 흩어 놓지 마세요. 실효 권한을 읽고 결정하는 하나의 함수를 앱 코드에 두세요.
can(account_id, feature_key, needed_value=1):
ent = get_effective_entitlement(account_id, feature_key, now)
if ent.type == "bool": return ent.value == true
if ent.type == "number": return ent.value >= needed_value
모든 호출이 can(...)을 사용하면 업그레이드와 애드온은 데이터 업데이트가 되고 코드 재작성 문제가 사라집니다.
예시 시나리오: 예기치 않은 문제 없는 업그레이드와 애드온
6명 규모의 고객 지원 팀이 Starter 플랜을 사용 중입니다. Starter는 에이전트 좌석 3개와 월 1,000건의 SMS를 포함합니다. 월 중간에 6명으로 늘어나 추가 5,000건 SMS 팩을 원합니다. 이걸 "if plan = Pro then..." 같은 특수 코드 없이 작동하게 하고 싶습니다.
1일: Starter로 시작
계정에 청구 기간(예: 1월 1일~1월 31일)을 가진 subscription을 생성하고, 플랜에 대한 subscription_item을 추가합니다.
체크아웃 시(또는 야간 작업으로) 해당 기간에 대한 엔타이틀먼트 부여를 기록합니다:
entitlement_grant:agent_seats, value3, startJan 1, endJan 31entitlement_grant:sms_messages, value1000, startJan 1, endJan 31
앱은 "그들이 어떤 플랜인가?"를 묻지 않고 "지금의 실효 권한은 무엇인가?"를 묻고 seats = 3, SMS = 1000을 얻습니다.
15일: 같은 날 Pro로 업그레이드하고 SMS 팩 추가
1월 15일에 Pro로 업그레이드(10좌석, 2,000 SMS 포함)합니다. 기존 부여를 수정하지 않고 새 레코드를 추가합니다:
- 기존 플랜 아이템(Starter) 종료를
Jan 15로 설정 - 새 플랜 아이템(Pro) 시작
Jan 15, 종료Jan 31생성 - 새 애드온 아이템(SMS Pack 5000) 시작
Jan 15, 종료Jan 31생성
같은 기간에 대한 부여를 추가합니다:
entitlement_grant:agent_seats, value10, startJan 15, endJan 31entitlement_grant:sms_messages, value2000, startJan 15, endJan 31entitlement_grant:sms_messages, value5000, startJan 15, endJan 31
1월 15일 즉시 일어나는 일은?
- 좌석: 실효 좌석은 10이 됩니다(좌석은 보통 "큰 값 우선" 규칙을 택함). 그날 추가로 3명을 더 초대할 수 있습니다.
- SMS: 실효 SMS는 남은 기간 동안 7,000이 됩니다(메시지 팩은 합산 규칙).
기존 사용량을 "이동"할 필요가 없습니다. 사용량 테이블은 계속 메시지 전송을 집계하고, 엔타이틀먼트 검사는 현재 실효 한도와 비교합니다.
25일: 다운그레이드를 예정하고 기간 종료까지 접근 유지
1월 25일에 2월 1일부터 Starter로 다운그레이드를 예약합니다. 1월의 부여는 손대지 않고 다음 기간에 대한 아이템(또는 부여)을 만듭니다:
subscription_item(Starter) 시작Feb 1, 종료Feb 28- 2월 1일부터 시작하는 SMS 팩 아이템 없음
결과: 1월 31일까지는 Pro 좌석과 SMS 팩을 유지하고, 2월 1일에 실효 좌석은 3으로 떨어지고 SMS는 새 기간의 Starter 한도로 리셋됩니다. 이 방식은 명확하고 AppMaster의 노코드 워크플로와도 잘 맞습니다: 날짜를 바꾸면 행이 생성되고 엔타이틀먼트 쿼리는 그대로입니다.
흔한 실수와 함정
대부분의 구독 버그는 청구 버그가 아니라 제품 전반에 흩어진 로직 때문에 생기는 접근 버그입니다. 요금제와 권한 스키마를 망가뜨리는 가장 빠른 방법은 "그들이 이걸 할 수 있나?"를 다섯 군데에서 따로 따지는 것입니다.
전형적인 실패는 UI, API, 백그라운드 작업에 규칙을 하드코딩하는 것입니다. UI는 버튼을 숨기고 API는 엔드포인트 차단을 잊고 야간 작업은 다른 기준을 체크하면 "가끔 작동한다"는 보고가 쌓입니다.
또 다른 함정은 plan_id 검사로 접근을 판단하는 것입니다. 처음에는 간단해 보입니다(플랜 A는 내보내기 가능, 플랜 B는 불가). 하지만 애드온, 그랜파더링, 무료 트라이얼, 엔터프라이즈 예외가 등장하면 무너집니다. "if plan is Pro then allow..." 같은 코드를 쓰면 영원히 유지해야 할 미로를 만드는 것입니다.
시간과 취소의 엣지 케이스
접근을 나타내는 boolean(예: has_export = true)만 저장하고 날짜를 붙이지 않으면 접근이 "고정"되거나 너무 일찍 제거되는 문제가 생깁니다. 취소, 환불, 차지백, 사이클 중간 다운그레이드 모두 시간 경계가 필요합니다.
피할 수 있는 간단한 검사:
- 모든 엔타이틀먼트 부여는 출처(플랜, 애드온, 수동 오버라이드)와 시간 범위를 가져야 합니다.
- 모든 접근 결정은 "now가 start와 end 사이인지"를 사용해야 합니다(널 end에 대한 명확 규칙 포함).
- 백그라운드 작업은 어제 상태를 가정하지 말고 실행 시점에 엔타이틀먼트를 재확인하세요.
청구와 접근을 섞지 마세요
팀들이 청구 기록과 접근 규칙을 같은 테이블에 섞어 넣어 문제를 만듭니다. 청구는 인보이스, 세금, 정산, 제공자 ID, 재시도 상태가 필요합니다. 접근은 명확한 기능 키와 시간 창이 필요합니다. 얽히면 청구 마이그레이션이 제품 장애로 번질 수 있습니다.
마지막으로 많은 시스템이 감사 추적을 생략합니다. 사용자가 "왜 내가 내보낼 수 있나?"를 묻는다면 다음과 같이 답할 수 있어야 합니다: "애드온 X에 의해 2026-01-01부터 2026-02-01까지 활성화됨" 또는 "지원팀이 수동으로 부여함, 티켓 1842". 이 정보 없이는 지원과 엔지니어링이 추측하게 됩니다.
AppMaster로 구축한다면 Data Designer 모델에 감사 필드를 남기고, 웹/모바일/스케줄된 흐름 모두가 사용하는 단일 Business Process로 "그들이 할 수 있나?" 검사를 두세요.
배포 전 빠른 체크리스트
스키마를 배포하기 전 실제 질문으로 한 번 더 점검하세요. 목표는 접근이 설명 가능하고, 테스트 가능하며, 변경에 친화적인 것입니다.
현실 점검 질문
한 명의 사용자와 한 기능을 골라 결과를 지원이나 재무에 설명할 수 있어야 합니다. "그들이 Pro에 속해 있어서"만 말할 수 있다면(또는 더 나쁘게는 "코드가 그렇게 되어 있어서") 누군가 중간에 업그레이드하거나 맞춤 거래가 생기면 고통을 겪게 됩니다.
이 빠른 체크리스트를 사용하세요:
- 데이터(구독 아이템, 애드온, 오버라이드, 시간 창)만으로 "왜 이 사용자가 접근을 가지는가?"를 설명할 수 있는가? 애플리케이션 코드를 읽지 않고.
- 모든 접근 검사는 플랜 이름(예: "Starter" 또는 "Business")이 아니라 안정적인 기능 키(예:
feature.export_csv)로 이루어지는가? 플랜 이름은 바뀌지만 기능 키는 바뀌면 안 됩니다. - 엔타이틀먼트에 트라이얼, 유예 기간, 예정된 취소를 포함한 명확한 시작/종료 시간이 있는가? 시간이 빠져 있으면 다운그레이드가 논쟁거리가 됩니다.
- 오버라이드 레코드 하나로 특정 고객의 접근을 부여하거나 제거할 수 있는가? 이렇게 하면 한 달 동안 "10좌석 추가" 같은 요구를 커스텀 코드 없이 처리할 수 있습니다.
- 업그레이드와 다운그레이드를 샘플 행 몇 개로 테스트했을 때 예측 가능한 결과가 나오는가? 시뮬레이션을 위해 복잡한 스크립트가 필요하면 모델이 너무 암묵적입니다.
실용적 테스트: 새 사용자, 월 중 업그레이드된 사용자, 취소한 사용자 세 명과 하나의 애드온(예: "추가 좌석" 또는 "고급 리포트")을 만들고 각자에 대해 접근 쿼리를 실행하세요. 결과가 명확하고 설명 가능하면 준비된 것입니다.
AppMaster 같은 도구를 쓰는 경우에도 같은 규칙을 지키세요: 하나의 쿼리 또는 하나의 Business Process가 "할 수 있나?"에 책임을 지도록 하여 모든 웹/모바일 화면이 같은 답을 사용하게 하세요.
다음 단계: 업그레이드를 유지보수하기 쉽게 만들기
업그레이드를 관리하기 쉬운 상태로 유지하는 최선의 방법은 생각보다 작게 시작하는 것입니다. 실제로 가치가 있는 기능 몇 개(5~10개면 충분)를 골라 하나의 엔타이틀먼트 검사 함수만 먼저 구현하세요: "이 계정이 지금 X를 할 수 있나?"를 한 곳에서 답할 수 있어야 합니다. 하나의 장소에서 답하지 못하면 업그레이드는 항상 위험하게 느껴질 것입니다.
한 번 그 검사 하나가 잘 작동하면 업그레이드 경로를 단순히 청구 행위가 아니라 제품 행동으로 취급하세요. 이상한 엣지 케이스를 잡는 가장 빠른 방법은 실제 고객 이동을 기반으로 한 작은 접근 테스트 세트를 작성하는 것입니다.
즉시 효과가 있는 실무적인 다음 단계:
- 최소 기능 카탈로그를 정의하고 각 플랜을 명확한 엔타이틀먼트 집합에 매핑하세요.
- 애드온을 플랜 규칙에 박아 넣지 말고 별도의 "아이템"으로 추가하여 엔타이틀먼트를 부여하거나 확장하게 하세요.
- 일반 경로에 대한 5~10개의 접근 테스트를 작성하세요(사이클 중 업그레이드, 갱신 시 다운그레이드, 애드온 추가 및 제거, 트라이얼에서 유료 전환, 유예 기간).
- 가격 변경은 데이터만으로 처리하세요: 플랜 행, 기능 매핑, 엔타이틀먼트 부여를 업데이트하고 애플리케이션 코드는 그대로 두세요.
- 습관을 만드세요: 새 플랜이나 애드온을 추가할 때마다 접근 동작이 예상대로임을 증명하는 적어도 하나의 테스트를 추가하세요.
노코드 백엔드를 사용하더라도 이 패턴을 깔끔하게 모델링할 수 있습니다. AppMaster의 Data Designer는 핵심 테이블(plans, features, subscriptions, subscription items, entitlements)을 구축하기에 적합하고 Business Process Editor는 접근 결정 흐름(활성 엔타이틀먼트 로드, 시간 창 적용, 허용/거부 반환)을 담아 Hand‑coding 없이 모든 호출이 동일한 로직을 사용하게 합니다.
혜택은 다음 번 가격 변경에서 바로 드러납니다. 규칙을 다시 쓰는 대신 데이터를 편집합니다: 기능을 Pro에서 애드온으로 옮기거나 엔타이틀먼트 기간을 바꾸거나 레거시 플랜의 이전 부여를 유지시킬 수 있습니다. 접근 로직은 안정적으로 남고 업그레이드는 코드 스프린트가 아니라 통제된 업데이트가 됩니다.
스키마를 빠르게 검증하고 싶다면 업그레이드 하나와 애드온 하나를 처음부터 끝까지 모델링해 보고 접근 테스트를 먼저 실행해 보세요.


