이 글에서 다루는 내용

RevenueCat을 처음 연동할 때 낯선 개념인 Entitlement와 Offering이 각각 무슨 뜻인지, 왜 존재하는지, 그리고 실제 앱 코드와 대시보드에서 어떻게 활용하는지를 정리합니다.

왜 이 개념이 필요한가

앱스토어마다 구독 상품 ID가 다르다. 같은 월간 구독이라도 플랫폼별로 ID가 제각각이다.

App Store:  com.ytylab.pro_monthly
Play Store: pro-monthly

이걸 앱 코드에서 직접 처리하면 플랫폼마다 분기 처리가 필요하고, 상품을 추가하거나 변경할 때마다 코드를 수정하고 앱을 다시 배포해야 한다. RevenueCat은 이 문제를 EntitlementOffering이라는 두 개의 추상화 레이어로 해결한다.

Entitlement — 권한 게이트

Entitlement는 "이 유저가 프리미엄 기능을 쓸 수 있는가"에 대한 Yes/No를 담당한다. 단어 뜻 그대로 자격, 권한이다. 플랫폼별로 다른 상품 ID를 전부 하나의 Entitlement에 묶어두면, 앱 코드에서는 Entitlement 이름 하나만 체크하면 된다.

naon_subscription (Entitlement)
├── pro-monthly (Play Store)
├── pro-yearly (Play Store)
├── com.ytylab.pro_monthly (App Store)
└── com.ytylab.pro_yearly (App Store)
// ❌ Entitlement 없이 → 플랫폼/상품마다 분기처리
if (productId == 'pro-monthly' || 
    productId == 'pro-yearly' ||
    productId == 'com.ytylab.pro_monthly' || ...) { }

// ✅ Entitlement 사용 → 한 줄로 끝
if (customerInfo.entitlements['naon_subscription']?.isActive == true) { }

나중에 평생 이용권을 추가해도 앱 코드 수정 없이 RevenueCat 대시보드에서 해당 상품을 Entitlement에 연결만 하면 된다.

Plus, 프리미엄 등 등급 구분도 Entitlement로

구독 티어가 여러 개라면 Entitlement를 여러 개 만들어 구분한다.

naon_plus (Entitlement)
├── plus-monthly
└── plus-yearly

naon_premium (Entitlement)
├── premium-monthly
└── premium-yearly
// Plus 유저인가?
info.entitlements['naon_plus']?.isActive

// Premium 유저인가?
info.entitlements['naon_premium']?.isActive

Offering — 상품 진열대

Offering은 "결제 화면에 어떤 상품을 보여줄 것인가"에 대한 구성이다. 단어 뜻 그대로 제공물, 제안이다. 하나의 Offering 안에 월간, 연간, 할인 패키지 등을 묶어서 구성할 수 있다.

Offering: default (Current ✅)
├── Package: monthly → pro-monthly 상품
└── Package: annual  → pro-yearly 상품

Offering: black_friday
├── Package: monthly → pro-monthly-sale 상품 (할인가)
└── Package: annual  → pro-yearly-sale 상품 (할인가)

Offering: new_user
└── Package: monthly → pro-monthly-first (첫달 무료)

앱 코드는 항상 동일하게 Current Offering만 가져온다.

final offerings = await Purchases.getOfferings();
final current = offerings.current; // Current로 설정된 Offering 자동 반환

for (final package in current.availablePackages) {
  // package.packageType == PackageType.monthly
  // package.packageType == PackageType.annual
}

Current 토글의 힘

대시보드에서 어떤 Offering에 Current 토글을 켜느냐에 따라 앱 업데이트 없이 가격과 상품 구성을 바꿀 수 있다.

평소      → default Offering 활성화 (정상가)
블랙프라이데이 → black_friday Offering 활성화 (할인가)
이벤트 후  → default로 다시 전환

마케팅과 이벤트 전략을 코드 밖에서 컨트롤할 수 있다는 점이 Offering의 가장 큰 장점이다.

할인 상품은 별도 등록이 필요한가

할인을 구현하는 방법은 두 가지다.

방법 1 — 스토어에 별도 할인 상품 등록 (비권장)Play Store나 App Store에 할인가로 새 상품을 직접 등록하는 방식이다. 번거롭고 스토어 심사도 다시 받아야 해서 잘 사용하지 않는다.

방법 2 — 스토어 프로모션 기능 활용 (권장)기존 상품을 그대로 두고, 스토어 자체 프로모션 기능으로 할인을 처리한다.

Play Store → 프로모션 코드 / 인트로 가격 설정
App Store  → Introductory Offer / Promotional Offer 설정

RevenueCat Offering에는 기존 상품만 연결해두고, 할인은 스토어가 자동으로 적용한다. 처음에는 기본 상품 2개로 시작하고 복잡한 프로모션이 필요할 때 고민해도 충분하다.

전체 구조 요약

[앱 코드]
    ↓ getOfferings()
[Offering: default] ← 대시보드에서 관리
    ├── Package: monthly
    └── Package: annual
         ↓ 연결
    [Products]
    ├── pro-monthly (Play Store)
    ├── pro-yearly (Play Store)
    ├── com.ytylab.pro_monthly (App Store)
    └── com.ytylab.pro_yearly (App Store)
         ↓ 구매 완료 시
[Entitlement: naon_subscription] ← isActive 체크만 하면 됨

한 줄 요약:

  • Entitlement = 권한 게이트 ("프리미엄 유저인가?")
  • Offering = 상품 진열대 ("어떤 상품을 팔 것인가?")