Skip to content

feat(subscription): RevenueCat KMP subscriptions + premium gate on AI chat#440

Open
plusmobileapps wants to merge 6 commits into
mainfrom
claude/revenuecat-kmp-subscriptions-2b1572
Open

feat(subscription): RevenueCat KMP subscriptions + premium gate on AI chat#440
plusmobileapps wants to merge 6 commits into
mainfrom
claude/revenuecat-kmp-subscriptions-2b1572

Conversation

@plusmobileapps

Copy link
Copy Markdown
Collaborator

What

Adds the RevenueCat Kotlin Multiplatform SDK and uses a premium entitlement to feature-gate the AI chat.

  • SubscriptionRepository is the source of truth for premium status, with its own public contract and a testing fake so other features gate/integration-test against it without the production impl.
  • Custom Compose paywall (SubscriptionBloc + SubscriptionScreen, built from Plus* components) — selectable packages, subscribe/restore, and loading/premium/unavailable/error states.
  • AI-chat gate is centralized in RootBlocImpl: non-subscribers get a "premium required" dialog at the root; confirming opens the paywall as a root child. Premium users open the chat directly. Covers every entry point (bottom-nav action, recipe detail, cook mode, More-tab AI row).
  • More-tab entry: a new "ChefMate Premium" row opens the paywall directly.

RevenueCat integration notes

  • purchases-kmp 3.3.0 (purchases-kmp-core + purchases-kmp-result). The SDK is Android/iOS only, so the gateway + initializer live in androidMain/iosMain with a JVM no-op (desktop is never premium — subscriptions are mobile-only). Verified compiling on Android, iOS, and JVM.
  • Configured at startup via SubscriptionInitializer (mirrors BugsnagInitializer), wired from MyApplication and iOSApp.swift. API keys come from BuildKonfig (REVENUECAT_ANDROID_API_KEY / REVENUECAT_IOS_API_KEY) and are blank-key-safe — a blank key skips setup.

Before this ships

  • Configure the RevenueCat dashboard: a premium entitlement + an offering, and set the Android/iOS API keys. Without keys the SDK no-ops and the paywall shows its "unavailable" state.

Testing

  • Unit: SubscriptionRepositoryImplTest (fake gateway), SubscriptionViewModelTest, and RootBlocTest gate cases (premium → chat; free → dialog → confirm → paywall) + Settings output test.
  • Snapshots: paywall light/dark + loading/premium/unavailable/error.
  • Robot flows: More-tab → paywall, and AI-chat gate → dialog → paywall (SubscriptionNavigationUiTest). Existing AI-chat nav tests now grant premium via a TestSubscriptionRepository wired into the test graph.
  • All new/affected suites pass on JVM; Android + iOS targets of the data module compile.

Follow-ups

  • Purchase-cancellation currently surfaces the generic error dialog (RevenueCat's userCancelled flag isn't yet mapped into common) — small follow-up.
  • Post-purchase auto-resume of the originally-requested AI chat is deferred (confirm → paywall → re-tap).
  • The "More" SettingsScreen still has no snapshot coverage (pre-existing gap; its preview blocs are private).

🤖 Generated with Claude Code

plusmobileapps and others added 6 commits July 17, 2026 08:38
Add the SubscriptionRepository contract (the source of truth for premium
status) with its testing fake, and a RevenueCat-backed impl. RevenueCat's
purchases-kmp SDK is Android/iOS only, so the gateway + initializer are
duplicated across androidMain/iosMain with a JVM no-op (desktop is never
premium). Configure the SDK at startup via a SubscriptionInitializer,
mirroring BugsnagInitializer, wired from MyApplication (Android) and
iOSApp.swift; keys come from BuildKonfig (blank-key-safe no-op).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the SubscriptionBloc + custom Compose paywall (SubscriptionScreen)
built from Plus* components: selectable packages, subscribe/restore CTAs,
loading/premium/unavailable/error states. The ViewModel drives purchase and
restore through SubscriptionRepository and reflects premium status live.
Also add the PremiumRequiredDialog used by the AI-chat gate. Includes
ViewModel unit tests over the repository fake.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Centralize the AI-chat premium gate in RootBlocImpl: premium users open the
chat directly; everyone else gets a "premium required" dialog (rendered at
the root) whose confirmation opens the paywall as a root child. All existing
AI-chat entry points (bottom nav, recipe detail, cook mode) route through the
gate. Add a "ChefMate Premium" row to the More tab that opens the paywall
directly, threaded through the Settings -> BottomNav -> Root output chain.
Includes RootBloc gate tests and the Settings output test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add public preview blocs (SubscriptionPreviews) and @previewTest screenshot
wrappers for the paywall — light/dark, plus loading, premium, unavailable,
and error states — with recorded reference images.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add SubscriptionRobot and a MoreRobot entry for the premium row, plus a
runRootBlocTest flow covering both paywall entry points: the More-tab row
and the AI-chat gate (dialog -> confirm -> paywall). Wire a
TestSubscriptionRepository into the test graph (replacing the production
impl) so tests can grant premium; the existing AI-chat navigation tests now
set premium so they still reach the chat.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
RevenueCat's iOS code is Swift, and its object files auto-link the Swift
back-compat static libs (swiftCompatibility56, swiftCompatibilityConcurrency,
…). Static frameworks are linked by Xcode, which supplies these, but the
Kotlin/Native test executables are linked by Gradle outside Xcode and fail
with "library 'swiftCompatibility56' not found" (linkDebugTestIosSimulatorArm64).
Point those test binaries at the active Xcode toolchain's Swift lib dir for
the target SDK. Guarded to macOS so non-Apple CI configuration is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant