diff --git a/revenuecat/skills/integrate-revenuecat/platforms/android.md b/revenuecat/skills/integrate-revenuecat/platforms/android.md index 570a9d1..6cd1155 100644 --- a/revenuecat/skills/integrate-revenuecat/platforms/android.md +++ b/revenuecat/skills/integrate-revenuecat/platforms/android.md @@ -66,15 +66,17 @@ class MyApplication : Application() { - Use the **Google Play** public SDK key: it starts with `goog_` (Amazon Appstore keys start with `amzn_`). - The SDK declares `com.android.vending.BILLING` in its own manifest; you do not need to add it. -- Minimum SDK: **21**. Confirm `minSdk` in the app module's `build.gradle(.kts)`. +- Minimum SDK: **23**, or **24** if you use `purchases-ui` for paywalls or Customer Center. Confirm `minSdk` in the app module's `build.gradle(.kts)`. - Proguard/R8: the SDK ships consumer rules, no extra config needed. ## Verify -Run the app. In **logcat**, filter by tag `Purchases` and look for: +Run the app with `Purchases.logLevel = LogLevel.DEBUG` set before `configure`. The SDK logs under the tag `[Purchases] - `, so filter on the text rather than an exact tag: ``` -Purchases: โ„น๏ธ [Purchases] - INFO: ๐Ÿ˜ปโ€๐Ÿ‘ผ Purchases is configured +adb logcat | grep "\[Purchases\]" ``` -A wrong API key shows up as an auth error on the first offerings fetch. No logs at all usually means `android:name=".MyApplication"` is missing from the manifest. +At configure time you should see debug lines containing `SDK Version - ` and `Package name - `. + +A wrong API key shows up as an auth error on the first offerings fetch. No `[Purchases]` lines at all, with the log level at DEBUG, usually means `configure` never ran, most often because `android:name=".MyApplication"` is missing from the manifest. A custom `Purchases.logHandler` also keeps these lines out of logcat. diff --git a/revenuecat/skills/integrate-revenuecat/platforms/kmp.md b/revenuecat/skills/integrate-revenuecat/platforms/kmp.md index 871b230..2f03f1a 100644 --- a/revenuecat/skills/integrate-revenuecat/platforms/kmp.md +++ b/revenuecat/skills/integrate-revenuecat/platforms/kmp.md @@ -4,7 +4,7 @@ ## Install -Find the latest stable release at and substitute that tag for `` in the snippet below. The KMP tag uses a `+` format (e.g. `2.10.2+17.55.1`), where the part before `+` is the KMP wrapper version and the part after is the bundled `purchases-hybrid-common` version. Use the full tag string as the artifact version first; if Gradle interprets the `+` as a wildcard, fall back to the wrapper portion only (e.g. `2.10.2`). If GitHub is unreachable, ask the user for a version to pin. +Find the latest stable release at and substitute that version for `` in the snippet below (for example `3.10.1`). Since 3.0.0, versions are plain, without the old `+` suffix. Current releases need Kotlin 2.3.20+ and Android API 23+ (24+ once you add `purchases-kmp-ui`). Use 3.10.0 or later: 3.0 to 3.9 can log iOS users out when they update from a version before 3.0.0, and 3.10.0 recovers them. If GitHub is unreachable, ask the user for a version to pin. In the shared module's `build.gradle.kts`: @@ -15,7 +15,7 @@ kotlin { sourceSets { commonMain.dependencies { implementation("com.revenuecat.purchases:purchases-kmp-core:") - // implementation("com.revenuecat.purchases:purchases-kmp-ui:") // Compose Multiplatform paywalls + // implementation("com.revenuecat.purchases:purchases-kmp-ui:") // Compose Multiplatform paywalls, needs Android minSdk 24 } } } @@ -25,7 +25,7 @@ Add `mavenCentral()` to your project repositories if it isn't already there. ### iOS linking -`purchases-kmp` bridges to `purchases-ios` on iOS. If you use CocoaPods with the Kotlin CocoaPods plugin, the bridge pod is wired automatically. For pure Swift Package Manager setups, follow the iOS target instructions in the [purchases-kmp README](https://github.com/RevenueCat/purchases-kmp#readme). The exact shape evolves per release. +Since 3.0.0 you add no CocoaPods pod or Swift package for the iOS SDK: `purchases-kmp` brings it along. Coming from 2.x, remove the `PurchasesHybridCommon` and `PurchasesHybridCommonUI` pods or packages ([3.0.0 migration guide](https://github.com/RevenueCat/purchases-kmp/blob/main/migrations/3.0.0-MIGRATION.md)). In the iOS source sets, opt in to `kotlinx.cinterop.ExperimentalForeignApi`, and if the project does not compile, set `isStatic = true` on the iOS framework binaries. ## Configure @@ -48,22 +48,17 @@ fun initRevenueCat(apiKey: String) { ### Android: call from `Application.onCreate()` -On Android, `PurchasesConfiguration` needs a `Context`. The KMP SDK provides a platform specific overload that takes the Android `Context` as the first argument. Use it from your `Application` class: +The KMP `PurchasesConfiguration.Builder` takes only the API key. On Android, `purchases-kmp` gets the `Application` context itself through AndroidX App Startup, so keep `androidx.startup.InitializationProvider` in the merged manifest (remove only specific initializers if you must). Call the shared function from your `Application` class: ```kotlin class MyApplication : Application() { override fun onCreate() { super.onCreate() - // The purchases-kmp Android actual of PurchasesConfiguration takes a Context. - // Pass it here before forwarding to the shared initRevenueCat, or construct - // the PurchasesConfiguration inline on this Android-only path. initRevenueCat("goog_YOUR_ANDROID_PUBLIC_SDK_KEY") } } ``` -> If the `Purchases.configure(...)` signature in your installed version of purchases-kmp requires a different shape on Android (e.g. `PurchasesConfiguration(context, apiKey)`), follow what the IDE autocompletes. The KMP SDK's `expect`/`actual` surface has changed across versions. Don't guess. - ### iOS: call from `@main App` (SwiftUI) ```swift @@ -82,12 +77,14 @@ struct iOSApp: App { ## Notes - Two public SDK keys: `appl_โ€ฆ` for iOS, `goog_โ€ฆ` for Android. Keep them separate. -- Because the KMP SDK wraps the native SDKs, the verify logs below are the native SDK logs. See `platforms/ios.md` / `platforms/android.md` for the exact log line on each side. -- When in doubt about the exact shape of `PurchasesConfiguration`, check the installed version's source or the [purchases-kmp README](https://github.com/RevenueCat/purchases-kmp). The skill prefers "accurate for your version" over "plausibly correct in general." +- Because the KMP SDK wraps the native SDKs, the verify logs below are the native SDK logs. +- When in doubt about the exact shape of `PurchasesConfiguration`, check the installed version's source or the [configuration docs](https://www.revenuecat.com/docs/getting-started/quickstart#2-initialize-and-configure-the-revenuecat-sdk). The skill prefers "accurate for your version" over "plausibly correct in general." ## Verify Run each platform target. Expect: -- **iOS**: `[Purchases] - INFO: ๐Ÿ˜ปโ€๐Ÿ‘ผ Purchases is configured` in Xcode console. -- **Android**: `Purchases: โ„น๏ธ [Purchases] - INFO: ๐Ÿ˜ปโ€๐Ÿ‘ผ Purchases is configured` in logcat. +- **iOS**: with `LogLevel.DEBUG`, the Xcode console shows a line containing `SDK Version - `. +- **Android**: with `LogLevel.DEBUG`, `adb logcat | grep "\[Purchases\]"` shows a line containing `SDK Version - `. + +On both targets `` is the native SDK's version, not the purchases-kmp version. diff --git a/revenuecat/skills/revenuecat-customer-center/platforms/android.md b/revenuecat/skills/revenuecat-customer-center/platforms/android.md index e8e0acf..35c7ee0 100644 --- a/revenuecat/skills/revenuecat-customer-center/platforms/android.md +++ b/revenuecat/skills/revenuecat-customer-center/platforms/android.md @@ -70,7 +70,7 @@ class SettingsActivity : ComponentActivity() { ## Notes -- `CustomerCenter` and `ShowCustomerCenter` require `purchases-ui` **8.x** or newer. If the installed version is older, fall back to rendering subscription state from `Purchases.sharedInstance.getCustomerInfo(โ€ฆ)` plus a restore button and a link to `https://play.google.com/store/account/subscriptions`. +- `CustomerCenter` requires `purchases-ui` **8.12.0** or newer, and `ShowCustomerCenter` **8.13.0** or newer. If the installed version is older, fall back to rendering subscription state from `Purchases.sharedInstance.getCustomerInfo(โ€ฆ)` plus a restore button and a link to `https://play.google.com/store/account/subscriptions`. - Refund requests are **not** supported on Android (refunds go through Google Play). Any refund related dashboard action degrades gracefully to either hiding the option or deep linking to the Play subscriptions page. - When the user taps **Manage subscription** on Android, the Customer Center opens the system subscriptions screen via an `Intent`. Your app returns to the foreground afterwards. - The composable requires a Material 3 theme in the Compose tree. `CustomerCenterActivity` sets one up for you; if you embed `CustomerCenter` inside your own Compose host, wrap it in `MaterialTheme { โ€ฆ }`. @@ -84,6 +84,6 @@ Sign into the app with a Google account that has at least one active sandbox sub 2. Tap **Restore purchases**. The active entitlements reload. If you wired `CustomerCenterOptions.listener`, its `onRestoreCompleted` callback fires. 3. Tap **Manage subscription**. The Google Play subscriptions screen opens in a new task. 4. Close the Customer Center. With the composable, `onDismiss` fires. With `ShowCustomerCenter`, the `ActivityResultCallback` runs. -5. `adb logcat -s Purchases` shows the customer info fetch and any transaction events. +5. `adb logcat | grep "\[Purchases\]"` shows the customer info fetch and any transaction events. If the view is empty for a user who has purchases, confirm `Purchases.sharedInstance.appUserID` matches the dashboard user who owns those transactions and that the dashboard's Customer Center section is configured. diff --git a/revenuecat/skills/revenuecat-customer-center/platforms/kmp.md b/revenuecat/skills/revenuecat-customer-center/platforms/kmp.md index d987190..eba9684 100644 --- a/revenuecat/skills/revenuecat-customer-center/platforms/kmp.md +++ b/revenuecat/skills/revenuecat-customer-center/platforms/kmp.md @@ -4,7 +4,7 @@ ## Install -Find the latest stable release at and substitute that tag for `` in the snippet below. The KMP tag uses a `+` format (e.g. `2.10.2+17.55.1`), where the part before `+` is the KMP wrapper version and the part after is the bundled `purchases-hybrid-common` version. Use the full tag string as the artifact version first; if Gradle interprets the `+` as a wildcard, fall back to the wrapper portion only (e.g. `2.10.2`). If GitHub is unreachable, ask the user for a version to pin. +Find the latest stable release at and substitute that version for `` in the snippet below (for example `3.10.1`). Since 3.0.0, versions are plain, without the old `+` suffix. Current releases need Kotlin 2.3.20+, and `purchases-kmp-ui` needs Android API 24+ (core alone needs 23+). Use 3.10.0 or later: 3.0 to 3.9 can log iOS users out when they update from a version before 3.0.0, and 3.10.0 recovers them. If GitHub is unreachable, ask the user for a version to pin. In the shared module's `build.gradle.kts`: @@ -21,7 +21,7 @@ kotlin { } ``` -Compose Multiplatform must already be set up on the shared module. On iOS, the UI artifact bridges to `RevenueCatUI`; for CocoaPods-based KMP projects, the Kotlin CocoaPods plugin wires the pod automatically. For pure SPM setups, follow the iOS linking section of the [purchases-kmp README](https://github.com/RevenueCat/purchases-kmp#readme). +Compose Multiplatform must already be set up on the shared module. On iOS, the UI artifact wraps the native `RevenueCatUI` Customer Center. Since 3.0.0 it needs no extra CocoaPods pod or Swift package. Coming from 2.x, remove `PurchasesHybridCommonUI` ([3.0.0 migration guide](https://github.com/RevenueCat/purchases-kmp/blob/main/migrations/3.0.0-MIGRATION.md)). ## Implement @@ -53,7 +53,7 @@ The common API is minimal: `modifier` and a required `onDismiss` callback. Platf - Customer Center requires iOS 15+ on the iOS target. On Android, refund flows degrade to deep linking into Google Play (Apple only feature). - The KMP common API exposes fewer hooks than either native SDK. If your product requires `onRestoreCompleted`, `onManagementOptionSelected`, or other lifecycle callbacks, wire them through platform code directly, or upgrade to a `purchases-kmp-ui` release that exposes them in common. -- If the IDE cannot resolve `com.revenuecat.purchases:purchases-kmp-ui`, confirm artifact coordinates against the [purchases-kmp README](https://github.com/RevenueCat/purchases-kmp) for your installed version; the module has evolved across 1.x. +- If the IDE cannot resolve `com.revenuecat.purchases:purchases-kmp-ui`, check the version against the [purchases-kmp releases](https://github.com/RevenueCat/purchases-kmp/releases) (current releases are 3.x) and the coordinates against the [Customer Center docs for Kotlin Multiplatform](https://www.revenuecat.com/docs/tools/customer-center/customer-center-kmp). - Ensure `Purchases.logIn(appUserId)` has run for signed in users before opening the Customer Center, or subscriptions will show up under an anonymous ID. ## Verify diff --git a/revenuecat/skills/revenuecat-entitlements-gate/platforms/kmp.md b/revenuecat/skills/revenuecat-entitlements-gate/platforms/kmp.md index cb95cdb..b1525aa 100644 --- a/revenuecat/skills/revenuecat-entitlements-gate/platforms/kmp.md +++ b/revenuecat/skills/revenuecat-entitlements-gate/platforms/kmp.md @@ -86,7 +86,7 @@ runCatching { - `Purchases.sharedInstance.delegate` holds a single reference. If you need to fan out to multiple consumers, funnel through a single repository (as shown) and expose a `StateFlow`. - Replace `"premium"` with the entitlement identifier configured in the RevenueCat dashboard. It is case sensitive. -- If your installed version of `purchases-kmp` does not expose `awaitCustomerInfo` in `com.revenuecat.purchases.kmp.ktx`, prefer what the IDE autocompletes and see the `purchases-kmp` README. Some versions ship result based variants in a separate module. +- If your installed version of `purchases-kmp` does not expose `awaitCustomerInfo` in `com.revenuecat.purchases.kmp.ktx`, prefer what the IDE autocompletes and see the [ktx package in the purchases-kmp API reference](https://revenuecat.github.io/purchases-kmp/core/com.revenuecat.purchases.kmp.ktx/). Some versions ship result based variants in a separate module. ## Verify diff --git a/revenuecat/skills/revenuecat-identify-user/platforms/kmp.md b/revenuecat/skills/revenuecat-identify-user/platforms/kmp.md index 81f713b..3b55c8c 100644 --- a/revenuecat/skills/revenuecat-identify-user/platforms/kmp.md +++ b/revenuecat/skills/revenuecat-identify-user/platforms/kmp.md @@ -88,7 +88,7 @@ Purchases.sharedInstance.logOut( - Use a stable opaque identifier (UUID / hash). Do not pass an email address, phone number, or a raw integer database id. - `Purchases.sharedInstance.isAnonymous` is available in commonMain and is the correct guard before calling `awaitLogOut`. -- If your installed version of `purchases-kmp` does not expose `awaitLogIn` / `awaitLogOut` in `com.revenuecat.purchases.kmp.ktx`, prefer what the IDE autocompletes and see the `purchases-kmp` README. Some versions ship `Result`-based variants in a separate module. +- If your installed version of `purchases-kmp` does not expose `awaitLogIn` / `awaitLogOut` in `com.revenuecat.purchases.kmp.ktx`, prefer what the IDE autocompletes and see the [ktx package in the purchases-kmp API reference](https://revenuecat.github.io/purchases-kmp/core/com.revenuecat.purchases.kmp.ktx/). Some versions ship `Result`-based variants in a separate module. ## Verify diff --git a/revenuecat/skills/revenuecat-migrate/platforms/android.md b/revenuecat/skills/revenuecat-migrate/platforms/android.md index 4e8143d..0cd0120 100644 --- a/revenuecat/skills/revenuecat-migrate/platforms/android.md +++ b/revenuecat/skills/revenuecat-migrate/platforms/android.md @@ -10,7 +10,7 @@ Use observer mode. Your existing Play Billing code keeps owning the purchase flo ### Install the SDK -See `integrate-revenuecat/platforms/android.md` for dependency specifics. Target a recent 8.x or newer release. +See `integrate-revenuecat/platforms/android.md` for dependency specifics. Target the current major release (10.x). It depends on Play Billing Library 8.3.0 and minSdk 23, so your existing Play Billing code must compile against Billing Library 8. ### Configure in observer mode @@ -74,7 +74,7 @@ Treat the SDK repo's docs as authoritative. Any version-specific diff written he After migration: 1. App builds at the new SDK version with `Purchases.logLevel = LogLevel.DEBUG`. -2. Logcat at launch shows `Purchases: โ„น๏ธ [Purchases] - INFO: ๐Ÿ˜ปโ€๐Ÿ‘ผ Purchases is configured`. +2. `adb logcat | grep "\[Purchases\]"` at launch shows the `SDK Version - ` debug line. 3. A sandbox purchase from a license tester on the Internal Testing track shows on the RevenueCat dashboard Sandbox view, attached to the correct appUserID. 4. A user with an existing active subscription still has it after relaunch. 5. Log level is dropped before the next release. diff --git a/revenuecat/skills/revenuecat-migrate/platforms/kmp.md b/revenuecat/skills/revenuecat-migrate/platforms/kmp.md index d395326..2e22ada 100644 --- a/revenuecat/skills/revenuecat-migrate/platforms/kmp.md +++ b/revenuecat/skills/revenuecat-migrate/platforms/kmp.md @@ -57,7 +57,7 @@ Treat the SDK repo's docs as authoritative. Any version-specific diff written he After migration: 1. Both iOS and Android targets build at the new version with debug logging on. -2. The native SDK configure banner appears in each target's platform console. +2. Each target's platform console shows a line containing `SDK Version - `, with the version of the native SDK that `purchases-kmp` bundles. 3. A sandbox purchase on each target shows on the RevenueCat dashboard. 4. An existing subscriber still has their entitlement active on each target. 5. Log level dropped before release. diff --git a/revenuecat/skills/revenuecat-paywall/platforms/android.md b/revenuecat/skills/revenuecat-paywall/platforms/android.md index ffc3600..7f0a84b 100644 --- a/revenuecat/skills/revenuecat-paywall/platforms/android.md +++ b/revenuecat/skills/revenuecat-paywall/platforms/android.md @@ -113,4 +113,4 @@ Run the app on a device or emulator signed into a Google sandbox tester: 1. Trigger the code path that opens the paywall. The template configured in the dashboard renders. 2. Tap a package and complete a test purchase. The paywall closes and either `PaywallListener.onPurchaseCompleted` fires (composable) or the result handler receives `PaywallResult.Purchased` (activity). 3. Dismiss with the close button. `dismissRequest` (composable) or `PaywallResult.Cancelled` (activity) fires. -4. Run `adb logcat -s Purchases` and confirm a successful transaction log line around the purchase. +4. Run `adb logcat | grep "\[Purchases\]"` and confirm a successful transaction log line around the purchase. diff --git a/revenuecat/skills/revenuecat-paywall/platforms/kmp.md b/revenuecat/skills/revenuecat-paywall/platforms/kmp.md index f82b92b..42b5ce8 100644 --- a/revenuecat/skills/revenuecat-paywall/platforms/kmp.md +++ b/revenuecat/skills/revenuecat-paywall/platforms/kmp.md @@ -4,7 +4,7 @@ ## Install -Find the latest stable release at and substitute that tag for `` in the snippet below. The KMP tag uses a `+` format (e.g. `2.10.2+17.55.1`), where the part before `+` is the KMP wrapper version and the part after is the bundled `purchases-hybrid-common` version. Use the full tag string as the artifact version first; if Gradle interprets the `+` as a wildcard, fall back to the wrapper portion only (e.g. `2.10.2`). If GitHub is unreachable, ask the user for a version to pin. +Find the latest stable release at and substitute that version for `` in the snippet below (for example `3.10.1`). Since 3.0.0, versions are plain, without the old `+` suffix. Current releases need Kotlin 2.3.20+, and `purchases-kmp-ui` needs Android API 24+ (core alone needs 23+). Use 3.10.0 or later: 3.0 to 3.9 can log iOS users out when they update from a version before 3.0.0, and 3.10.0 recovers them. If GitHub is unreachable, ask the user for a version to pin. In the shared module's `build.gradle.kts`, add the UI artifact to `commonMain`: @@ -23,7 +23,7 @@ kotlin { Compose Multiplatform must already be set up in the shared module (the `org.jetbrains.compose` plugin and the compose dependencies). If it is not, `Paywall` will not compile. -On iOS, the UI artifact bridges through the `RevenueCatUI` framework. Make sure your Kotlin framework links against it. For setups using the Kotlin CocoaPods plugin, the pod integration handles this automatically. For pure SPM setups, follow the iOS linking section of the [purchases-kmp README](https://github.com/RevenueCat/purchases-kmp#readme). +On iOS, the UI artifact wraps the native `RevenueCatUI` paywalls. Since 3.0.0 it needs no extra CocoaPods pod or Swift package. Coming from 2.x, remove `PurchasesHybridCommonUI` ([3.0.0 migration guide](https://github.com/RevenueCat/purchases-kmp/blob/main/migrations/3.0.0-MIGRATION.md)). ## Implement @@ -83,7 +83,7 @@ Set a specific offering via the `offering` property on the builder. If left `nul - The `PaywallListener` callbacks, `PaywallOptions` surface, and the `dismissRequest` contract mirror the native Android SDK. See `purchases-android` docs for detailed semantics. - Do not call `Purchases.purchase(โ€ฆ)` from outside the paywall. The paywall runs the purchase itself and calls the listener with the result. -- The exact group/artifact coordinates for `purchases-kmp-ui` have evolved across 1.x releases. If the IDE flags the dependency as unresolved, prefer what shows up in the [purchases-kmp README](https://github.com/RevenueCat/purchases-kmp) for your installed version over the snippet above. +- `com.revenuecat.purchases:purchases-kmp-ui` is the current coordinate for 3.x. If the IDE flags the dependency as unresolved, check the version first, then the [Paywalls installation docs for Kotlin Multiplatform](https://www.revenuecat.com/docs/tools/paywalls/installation#kotlin-multiplatform-installation). - Compose Multiplatform paywalls require the Compose runtime on both targets. Desktop, web, and other non mobile targets are not supported by the paywall module. ## Verify @@ -93,4 +93,4 @@ Run the Android target and the iOS target, each with a sandbox tester account: 1. Trigger the composable on each platform. The dashboard configured paywall renders inside the shared Compose host. 2. Complete a sandbox purchase. The paywall dismisses via `dismissRequest` and `PaywallListener.onPurchaseCompleted` fires with a non-null `CustomerInfo`. 3. Dismiss without purchasing. `dismissRequest` fires with no listener call. -4. Inspect logs on each platform (Xcode console for iOS, `adb logcat -s Purchases` for Android) to confirm the underlying native SDK ran the transaction. +4. Inspect logs on each platform (Xcode console for iOS, `adb logcat | grep "\[Purchases\]"` for Android) to confirm the underlying native SDK ran the transaction. diff --git a/revenuecat/skills/revenuecat-testing-setup/SKILL.md b/revenuecat/skills/revenuecat-testing-setup/SKILL.md index 7bd0d72..2b02b97 100644 --- a/revenuecat/skills/revenuecat-testing-setup/SKILL.md +++ b/revenuecat/skills/revenuecat-testing-setup/SKILL.md @@ -21,7 +21,7 @@ If several match (e.g. an `ios/` folder inside a Flutter project), pick the **ou ## 2. Shared concepts (all platforms) -### You cannot charge real money during development +### Test without charging real money Each store has a dedicated testing channel. Choosing the right channel depends on what you want to test. @@ -29,19 +29,19 @@ Each store has a dedicated testing channel. Choosing the right channel depends o Higher fidelity exercises more of the real purchase pipeline. Lower fidelity iterates faster. -- **RevenueCat Test Store (lowest fidelity, fastest iteration, deterministic).** A synthetic store hosted by RevenueCat. The SDK is configured with a `test_โ€ฆ` API key from the dashboard. Purchases open a Test Store dialog where you pick the outcome by hand: Successful Purchase, Failed Purchase, or Cancel. Purchases trigger entitlements, update `CustomerInfo`, and appear on the dashboard, but no Apple or Google call happens. Best for paywall iteration, integration tests, and CI smoke runs. +- **RevenueCat Test Store (lowest fidelity, fastest iteration, deterministic).** A synthetic store hosted by RevenueCat. The SDK is configured with a `test_โ€ฆ` API key from the dashboard. Purchases open a Test Store dialog where you pick the outcome by hand: Test valid purchase, Test failed purchase, or Cancel. Purchases trigger entitlements, update `CustomerInfo`, and appear on the dashboard, but no Apple or Google call happens. Best for paywall iteration, integration tests, and CI smoke runs. - **iOS StoreKit Configuration File (low fidelity, Apple synthetic).** Xcode stubs the store locally. Purchases succeed instantly with no App Store Connect round trip. Useful when you need StoreKit specific behavior. RevenueCat transactions in this mode may or may not appear on the dashboard depending on SDK version and intended routing, so it is not a faithful dashboard test. -- **iOS Sandbox (real sandbox Apple ID) / Android Internal Testing (license tester).** Real store backends, real RevenueCat dashboard ingestion, real receipts. Slower to iterate: build, install, wait for Play propagation or App Store Connect to register the product. -- **TestFlight (iOS) / Closed or Open Testing (Android).** Behaves very close to production. Receipts are production style. Transactions land in the **production** RevenueCat dashboard, not the Sandbox view. +- **iOS Sandbox (real sandbox Apple ID) / Android license tester on an internal or closed testing track.** Real store backends, real RevenueCat dashboard ingestion, real receipts. Slower to iterate: build, install, wait for Play propagation or App Store Connect to register the product. +- **TestFlight (iOS) / any Play testing track (Android, accounts that are not license testers).** Behaves very close to production. Receipts are production style. Transactions land in the **production** RevenueCat dashboard, not the Sandbox view. On Android these are real charges. - **Production.** Real money. Do not use for testing. -Start with the lowest fidelity that answers the question, then move up. UI and paywall iteration belongs in the fast channel. "Does my entitlement actually flip?" belongs in sandbox or internal testing. +Start with the lowest fidelity that answers the question, then move up. UI and paywall iteration belongs in the fast channel. "Does my entitlement actually flip?" belongs in the store sandbox, which on Android means a license tester on a testing track. ### Test Store: when to reach for it before sandbox RevenueCat's Test Store is a synthetic store provider configured per project on the dashboard. It produces real RevenueCat backend records (`CustomerInfo` updates, entitlement transitions, dashboard transactions) without calling App Store or Google Play. The price of that speed is fidelity. Test Store does not simulate Ask to Buy approval flows, region specific pricing, server side receipt validation specifics, store level review or rejection paths, or full subscription renewal cadence (Test Store renews up to five times on a compressed clock). -Use Test Store when you want to iterate on UI quickly, write integration tests with deterministic outcomes, or run CI checks. Move up to App Store Sandbox or Google Play Internal Testing for anything that exercises store side behavior. +Use Test Store when you want to iterate on UI quickly, write integration tests with deterministic outcomes, or run CI checks. Move up to App Store Sandbox or a Google Play license tester on a testing track for anything that exercises store side behavior. **Setup.** In the dashboard, go to **Apps and providers** โ†’ _Test configuration_ section โ†’ **Test Store** โ†’ Create / Enable. The Test Store API key appears under **Project Settings โ†’ API keys** with a `test_` prefix. Configure the SDK with the test key in debug builds and the production key (`appl_โ€ฆ` / `goog_โ€ฆ`) in release builds. The platform files show the per platform key swap pattern. diff --git a/revenuecat/skills/revenuecat-testing-setup/platforms/android.md b/revenuecat/skills/revenuecat-testing-setup/platforms/android.md index a1daf32..d06c82b 100644 --- a/revenuecat/skills/revenuecat-testing-setup/platforms/android.md +++ b/revenuecat/skills/revenuecat-testing-setup/platforms/android.md @@ -44,13 +44,13 @@ Purchases.configure( ### Step 3: trigger a purchase -The first call to `Purchases.sharedInstance.awaitPurchase(...)` (or the callback variant) opens the Test Store dialog with three buttons: **Successful Purchase**, **Failed Purchase**, **Cancel**. Pick the outcome you want to test. +The first call to `Purchases.sharedInstance.awaitPurchase(...)` (or the callback variant) opens the Test Store dialog with three buttons: **Test valid purchase**, **Test failed purchase**, **Cancel**. Pick the outcome you want to test. ### Verify 1. Logcat shows the SDK configured with a `test_` key. -2. **Successful Purchase** routes through the success path; the entitlement on the returned `CustomerInfo` is active. -3. **Failed Purchase** and **Cancel** route through the same `PurchasesException` / `userCancelled` paths your app already handles for real Play purchases. +2. **Test valid purchase** routes through the success path; the entitlement on the returned `CustomerInfo` is active. +3. **Test failed purchase** throws with `PurchasesErrorCode.TestStoreSimulatedPurchaseError`, and **Cancel** throws with `userCancelled = true`. Both go through the error paths your app already handles for real Play purchases. ## Per store: which sandbox applies @@ -63,11 +63,11 @@ Test Store described above works for any target store and stays as is. ## Real sandbox: Google Play license tester on Internal Testing -Real sandbox testing on Google Play requires all three of: a license tester account, a signed build on the Internal Testing track, and live (not draft) products in Play Console. +Real sandbox testing on Google Play needs an account that is a license tester and has opted in to a testing track (internal or closed) through the track's opt-in link, a build uploaded to that track, and live (not draft) products. Without the opt-in, products do not load. Accounts that are not license testers are charged real money, even on a testing track. ## Add license testers -1. Google Play Console โ†’ **Setup โ†’ License testing**. +1. Google Play Console โ†’ **Settings โ†’ License testing**. 2. Add the Gmail accounts that will be used for testing. 3. Set **License response** to `RESPOND_NORMALLY`. Other values simulate error conditions. @@ -75,15 +75,15 @@ License testers pay a cartoon price instead of real money when they buy licensed ## Upload to the Internal Testing track -This is the step most often skipped. A sideloaded debug build with a debug signing key **cannot** purchase licensed products, even for license testers. +Play needs the app on a testing track, and each license tester must opt in through the track link, before test purchases work. After that, a license tester can also test sideloaded builds, including debug builds with debug signatures, because Google lets license testers bypass the check that blocks apps not uploaded to Google Play. 1. Build a **signed** AAB (release or a dedicated test variant). The signing key must match the upload key expected by Play Console, or be uploaded under Play App Signing. -2. Play Console โ†’ **Release โ†’ Testing โ†’ Internal testing** โ†’ create a release, upload the AAB. +2. Play Console โ†’ **Test and release โ†’ Testing โ†’ Internal testing** โ†’ create a release, upload the AAB. 3. Add your license tester Gmail accounts to the tester list for this track. 4. Publish the release to the track. -5. Share the opt-in link with testers. Each tester must click it, accept, and install the app **from the Play Store link** on their device. +5. Share the opt-in link with testers. Each tester must click it and accept. Testers can then install the app from the Play Store link, and license testers can also run a sideloaded build. -Fresh uploads take roughly 15 minutes to propagate through Play. If a tester opens the opt-in link and the Play Store says "not available in your country" or similar, wait and retry. +After you publish to a testing track, it can take a few hours before the app is available to testers. If a tester opens the opt-in link and the Play Store says "not available in your country" or similar, wait and retry. ## Verify products are active in Play Console @@ -106,7 +106,7 @@ Flavor builds with suffixed application IDs (`com.example.myapp.dev`) need their ## Run and purchase -Install via the Play opt-in link (not Android Studio, not `adb install`). Sign into the device with a license tester account. Trigger the purchase. Play shows a "test card, always approves" dialog. On confirmation, the purchase flows through Play Billing into RevenueCat. +Opt in through the Play link first. Then install from Play, or, on a license tester's device, sideload the build from Android Studio or `adb install`. Sign into the device with a license tester account. Trigger the purchase. Play shows a "test card, always approves" dialog. On confirmation, the purchase flows through Play Billing into RevenueCat. The transaction should appear on the RevenueCat dashboard **Sandbox** view within seconds. @@ -128,7 +128,7 @@ Play Console offers **subscription test accounts** with configurable test clocks ## Verify 1. `Purchases.logLevel = LogLevel.DEBUG` in the build. -2. App is installed from the Play Internal Testing opt-in link. +2. The license tester account has opted in to the testing track, and the app is installed from Play or sideloaded. 3. Device is signed in with a license tester Gmail. 4. A purchase succeeds, Play shows "test card, always approves". 5. Logcat shows the SDK posting the purchase. diff --git a/revenuecat/skills/revenuecat-testing-setup/platforms/flutter.md b/revenuecat/skills/revenuecat-testing-setup/platforms/flutter.md index 0e9add2..46d1348 100644 --- a/revenuecat/skills/revenuecat-testing-setup/platforms/flutter.md +++ b/revenuecat/skills/revenuecat-testing-setup/platforms/flutter.md @@ -37,7 +37,7 @@ await Purchases.configure(PurchasesConfiguration(_apiKey())); Or use Flutter flavors: each flavor compiles its own API key constant and maps to its own RevenueCat dashboard app entry. -Trigger a purchase. The Test Store dialog opens with **Successful Purchase**, **Failed Purchase**, **Cancel**. Pick an outcome and verify the resulting `CustomerInfo` and dashboard transaction. +Trigger a purchase. The Test Store dialog opens with **Test valid purchase**, **Test failed purchase**, **Cancel**. Pick an outcome and verify the resulting `CustomerInfo` and dashboard transaction. The `kReleaseMode` branch above ensures the test key string is dead code in release builds and gets eliminated. @@ -46,7 +46,7 @@ The `kReleaseMode` branch above ensures the test key string is dead code in rele Follow the platform files directly: - iOS โ†’ `revenuecat-testing-setup/platforms/ios.md`. Prefer a real sandbox Apple ID for RevenueCat dashboard verification. -- Android โ†’ `revenuecat-testing-setup/platforms/android.md`. License tester on the Internal Testing track is required. +- Android โ†’ `revenuecat-testing-setup/platforms/android.md`. Test purchases need a license tester who has opted in to an internal or closed testing track. ## Build and install the right build @@ -59,8 +59,8 @@ Flutter supports multiple ways to install a test build. Only the signed, correct ### Android -- `flutter build appbundle --release` โ†’ signed AAB โ†’ upload to Play Internal Testing โ†’ install via the tester opt-in link. This is the only supported path for sandbox purchases. -- `flutter run` produces a debug build signed with the debug key. It **will not** be able to make sandbox purchases, because Play rejects the signature. +- `flutter build appbundle --release` โ†’ signed AAB โ†’ upload to Play Internal Testing โ†’ testers opt in through the track link and install from Play. +- `flutter run` produces a debug build signed with the debug key. It can make sandbox purchases only for a license tester who has opted in to the testing track. Other accounts either cannot buy from it or are charged real money. ## Multi flavor apps @@ -89,7 +89,7 @@ The Dart `print` output does not show native SDK logs. ## Verify 1. `await Purchases.setLogLevel(LogLevel.debug);` before `configure`. -2. The right build (signed AAB from Play Internal Testing on Android; development build on a sandbox device on iOS) is installed. +2. The right build is installed: on Android, by a license tester who opted in to the testing track, either from that track or as a debug build; on iOS, a development build on a sandbox device. 3. A test purchase succeeds. Native console shows the SDK posting the transaction. 4. The RevenueCat dashboard Sandbox view shows the transaction on the expected `appUserID`. 5. `await Purchases.getCustomerInfo()` shows the expected entitlement active. diff --git a/revenuecat/skills/revenuecat-testing-setup/platforms/ios.md b/revenuecat/skills/revenuecat-testing-setup/platforms/ios.md index ae4a33d..8b52c06 100644 --- a/revenuecat/skills/revenuecat-testing-setup/platforms/ios.md +++ b/revenuecat/skills/revenuecat-testing-setup/platforms/ios.md @@ -48,13 +48,13 @@ If you prefer Swift over Info.plist plumbing, branch in code with `#if DEBUG` an ### Step 3: trigger a purchase -The first call to `Purchases.shared.purchase(package:)` opens the Test Store dialog with three buttons: **Successful Purchase**, **Failed Purchase**, **Cancel**. Pick an outcome and verify the resulting `CustomerInfo` matches the path you expected. +The first call to `Purchases.shared.purchase(package:)` opens the Test Store dialog with three buttons: **Test valid purchase**, **Test failed purchase**, **Cancel**. Pick an outcome and verify the resulting `CustomerInfo` matches the path you expected. ### Verify 1. Xcode console shows the SDK configured with a `test_` key. -2. Picking **Successful Purchase** flips the entitlement on the returned `CustomerInfo`; the dashboard records the transaction. -3. Picking **Failed Purchase** or **Cancel** routes through the same error / `userCancelled` paths your app already handles for the real store. +2. Picking **Test valid purchase** flips the entitlement on the returned `CustomerInfo`; the dashboard records the transaction. +3. Picking **Test failed purchase** or **Cancel** routes through the same error / `userCancelled` paths your app already handles for the real store. ## Option B: StoreKit Configuration File (fastest, lowest fidelity) diff --git a/revenuecat/skills/revenuecat-testing-setup/platforms/kmp.md b/revenuecat/skills/revenuecat-testing-setup/platforms/kmp.md index 2dcad94..8f218a2 100644 --- a/revenuecat/skills/revenuecat-testing-setup/platforms/kmp.md +++ b/revenuecat/skills/revenuecat-testing-setup/platforms/kmp.md @@ -18,14 +18,14 @@ fun initRevenueCat(apiKey: String) { } ``` -The Test Store dialog and outcome semantics are identical to the native SDKs: **Successful Purchase**, **Failed Purchase**, **Cancel**. +The Test Store dialog and outcome semantics come from the native SDKs. On both platforms the buttons are **Test valid purchase**, **Test failed purchase**, and **Cancel**. ## Set up each target Follow the platform files directly: - iOS target โ†’ `revenuecat-testing-setup/platforms/ios.md`. Prefer a real sandbox Apple ID when exercising RevenueCat dashboard ingestion. The StoreKit Configuration File is useful for pure UI iteration but does not hit the real sandbox pipeline. -- Android target โ†’ `revenuecat-testing-setup/platforms/android.md`. License tester on the Internal Testing track is required. +- Android target โ†’ `revenuecat-testing-setup/platforms/android.md`. A license tester account is required; that file covers sideloaded builds and testing tracks. ## Match API keys @@ -63,7 +63,7 @@ Same as the native platforms: On each target: 1. `Purchases.logLevel = LogLevel.DEBUG`. -2. Native platform console shows the configure banner and the posted transaction. +2. Native platform console shows a line containing `SDK Version - ` at configure, then the posted transaction. 3. RevenueCat dashboard Sandbox view shows the transaction on the expected `appUserID`. 4. `customerInfo` after the purchase shows the expected entitlement active. 5. Restoring purchases on a fresh install brings the entitlement back. diff --git a/revenuecat/skills/revenuecat-testing-setup/platforms/react-native.md b/revenuecat/skills/revenuecat-testing-setup/platforms/react-native.md index c543e94..7948ed9 100644 --- a/revenuecat/skills/revenuecat-testing-setup/platforms/react-native.md +++ b/revenuecat/skills/revenuecat-testing-setup/platforms/react-native.md @@ -40,7 +40,7 @@ For Expo, use `eas.json` build profiles (`development`, `preview`, `production`) } ``` -Trigger a purchase. The Test Store dialog opens with **Successful Purchase**, **Failed Purchase**, **Cancel**. Pick an outcome and verify the resulting `customerInfo` and dashboard transaction. +Trigger a purchase. The Test Store dialog opens with **Test valid purchase**, **Test failed purchase**, **Cancel**. Pick an outcome and verify the resulting `customerInfo` and dashboard transaction. `__DEV__` is `false` in release builds, so the conditional above keeps the test key out of production binaries via dead code elimination. @@ -49,7 +49,7 @@ Trigger a purchase. The Test Store dialog opens with **Successful Purchase**, ** Follow the platform files directly: - iOS โ†’ `revenuecat-testing-setup/platforms/ios.md`. Prefer a real sandbox Apple ID for RevenueCat dashboard verification. -- Android โ†’ `revenuecat-testing-setup/platforms/android.md`. License tester on the Internal Testing track is required. +- Android โ†’ `revenuecat-testing-setup/platforms/android.md`. Test purchases need a license tester who has opted in to an internal or closed testing track. ## Dev client is required (Expo) @@ -72,8 +72,8 @@ Install the dev client and reload. Expo Go itself cannot be fixed to support pur ### Android -- `eas build --profile preview` (or `cd android && ./gradlew bundleRelease`) produces a signed AAB. Upload to Play Internal Testing. Install via the tester opt-in link. -- `npx react-native run-android` produces a debug build signed with the debug key. Play will **not** allow sandbox purchases against this signature. +- `eas build --profile production` (or `cd android && ./gradlew bundleRelease`) produces a signed AAB. Upload to Play Internal Testing. Install via the tester opt-in link. +- `npx react-native run-android` produces a debug build signed with the debug key. It can make sandbox purchases only for a license tester who has opted in to the testing track. Other accounts either cannot buy from it or are charged real money. ## applicationId / bundle identifier must match the dashboard @@ -108,7 +108,7 @@ Stale caches let the JS side bind to an old native module and produce confusing ## Verify 1. `Purchases.setLogLevel(LOG_LEVEL.DEBUG);` before `configure`. -2. You are running a dev client (not Expo Go), and the Android build is a signed AAB installed from Play Internal Testing. +2. You are running a dev client (not Expo Go). On Android, a license tester who opted in to the testing track installed it, either from that track or as a debug build. 3. A test purchase succeeds. The native platform console shows the SDK posting the transaction. 4. The RevenueCat dashboard Sandbox view shows the transaction on the expected `appUserID`. 5. `await Purchases.getCustomerInfo()` shows the expected entitlement active. diff --git a/revenuecat/skills/revenuecat-troubleshoot/SKILL.md b/revenuecat/skills/revenuecat-troubleshoot/SKILL.md index 7003f49..88277bd 100644 --- a/revenuecat/skills/revenuecat-troubleshoot/SKILL.md +++ b/revenuecat/skills/revenuecat-troubleshoot/SKILL.md @@ -36,7 +36,7 @@ Walk these nine items in order. Most reports are resolved by steps 1 through 5. List the apps using the `list-apps` RevenueCat MCP tool. The `bundle_id` (iOS / App Store) or `package_name` (Android / Play Store / Amazon Appstore) registered there must match the built app exactly, including capitalization. A mismatch causes offerings to come back empty because the app is not recognized. 4. **Verify offerings in the RevenueCat project.** List the project's offerings using the `list-offerings` RevenueCat MCP tool, passing the parameter `expand=items.package.product`. The offering marked with `is_current: true` must have at least one package attached, and each package must reference a store product. An offering with zero packages returns an empty `availablePackages` list even though `getOfferings` succeeds. 5. **Verify store products are live.** Products must be in "Ready to Submit" on App Store Connect or "Active" on Google Play Console. A product in a draft state will not be returned by the store, even in sandbox. If the SDK logs show offerings arriving from RevenueCat but products failing to resolve, this is almost always the cause. Use the `get-product-store-state` RevenueCat MCP tool to understand the state of product in App Store Connect or Google Play Console (field `store_status`). To fix or change the store state (not just read it), follow the `revenuecat-store-state` skill. -6. **Verify the testing account.** iOS: the device must be signed into a Sandbox Apple ID under Settings โ†’ App Store โ†’ Sandbox Account (set on iOS 14+ after the first sandbox prompt). Android: the tester's Gmail must be added to Google Play Console โ†’ Setup โ†’ License testing, and the app must be installed via the Internal Testing opt-in link, not sideloaded. +6. **Verify the testing account.** iOS: the device must be signed into a Sandbox Apple ID under Settings โ†’ App Store โ†’ Sandbox Account (set on iOS 14+ after the first sandbox prompt). Android: the tester's Gmail must be added to Google Play Console โ†’ Settings โ†’ License testing, and the tester must have opted in to a testing track (Internal Testing works) through its opt-in link. The app can then come from Play or be a sideloaded build with the same package name. 7. **Verify the network.** Corporate VPNs, captive portals, and some DNS filters silently block the RevenueCat API or the store APIs. Try a different network before digging deeper. 8. **Verify the appUserID.** If `logIn(appUserID)` was called with an ID that does not match what the user expects, entitlements appear missing because they are attached to a different RC user. Print `Purchases.shared.appUserID` (iOS) / `Purchases.sharedInstance.appUserID` (Android) and confirm it matches. 9. **Reset and retry.** Uninstall the app, re-sign into the sandbox / tester account, reinstall from the correct channel, relaunch. @@ -220,7 +220,7 @@ Log emoji indicators: ๐ŸŽ Apple/StoreKit ยท ๐Ÿค– Google Play ยท ๐Ÿ“ฆ Amazon ยท ### Android -- [ ] App published to at least closed testing track (internal testing won't work). +- [ ] App uploaded to a testing track (internal or closed), and the license tester opted in through the track link. License testers can then also test sideloaded builds. - [ ] Test account added as licensed tester in Play Console. - [ ] Service account credentials (JSON) uploaded to RevenueCat with Finance permissions. - [ ] Subscriptions use `product_id:base_plan_id` format. diff --git a/revenuecat/skills/revenuecat-troubleshoot/platforms/android.md b/revenuecat/skills/revenuecat-troubleshoot/platforms/android.md index d0e1c99..1a2ca8b 100644 --- a/revenuecat/skills/revenuecat-troubleshoot/platforms/android.md +++ b/revenuecat/skills/revenuecat-troubleshoot/platforms/android.md @@ -11,19 +11,20 @@ import com.revenuecat.purchases.Purchases Purchases.logLevel = LogLevel.DEBUG ``` -Set this before `Purchases.configure(...)` in your `Application.onCreate()`. Filter logcat by the `Purchases` tag: +Set this before `Purchases.configure(...)` in your `Application.onCreate()`. The SDK logs under the tag `[Purchases] - ` (for example `[Purchases] - DEBUG`), so `adb logcat -s Purchases` matches nothing. Filter on the text instead: ``` -adb logcat -s Purchases +adb logcat | grep "\[Purchases\]" ``` -Expected configure banner: +Expected configure lines (debug level): ``` -Purchases: โ„น๏ธ [Purchases] - INFO: ๐Ÿ˜ปโ€๐Ÿ‘ผ Purchases is configured +SDK Version - +Package name - ``` -If you see no `Purchases` tag output at all, `android:name=".MyApplication"` is missing from `AndroidManifest.xml` and the `Application` subclass never runs. +If you see no `[Purchases]` lines at all with the log level at DEBUG, `configure` usually never ran: `android:name=".MyApplication"` is missing from `AndroidManifest.xml`, so the `Application` subclass never runs. A custom `Purchases.logHandler` also keeps these lines out of logcat. ## License testers and the Internal Testing track @@ -31,13 +32,13 @@ This is the single most common Android gotcha. To make a test purchase on Android you need all of the following: -1. The tester's Gmail is added under **Google Play Console โ†’ Setup โ†’ License testing**. -2. The app has been uploaded as a signed AAB to an **Internal Testing** track. -3. The tester has opted in via the Internal Testing opt-in link and installed the app **from the Play Store** (not via `adb install` or Android Studio direct install). Sideloaded builds with the same package name do not have access to licensed products. -4. The SHA-1 certificate fingerprint of the signing key used to build the AAB matches what Play Console expects for that track. Play App Signing changes the effective key; check the upload key and app signing key in Play Console โ†’ Setup โ†’ App signing. -5. Fresh uploads take roughly 15 minutes to propagate through Play. If you just uploaded, wait before retesting. +1. The tester's Gmail is added under **Google Play Console โ†’ Settings โ†’ License testing**. +2. The app has been uploaded to a testing track such as **Internal Testing**, and the tester has opted in through that track's opt-in link. Without the opt-in, products do not load. +3. The build has the same package name as the app in Play Console. A license tester can then also use a sideloaded build, including a debug build with a debug signature, because Google lets license testers bypass the check that blocks apps not uploaded to Google Play. +4. Any AAB you upload is signed with the upload key Play Console expects. Play App Signing re-signs what Play delivers; check the upload key and app signing key on the **Play app signing** page in Play Console. +5. After you publish to a testing track, it can take a few hours before the app is available to testers. If you just uploaded, wait before retesting. -Sideloaded debug builds with a `debug` signing key cannot buy real or sandbox Play products. This is a Play Billing constraint, not a RevenueCat one. +Accounts that are not license testers either cannot buy or are charged real money, whether the build is sideloaded or installed from a testing track. For test purchases, add the account as a license tester. This is a Play Billing constraint, not a RevenueCat one. ## Products return empty list diff --git a/revenuecat/skills/revenuecat-troubleshoot/platforms/kmp.md b/revenuecat/skills/revenuecat-troubleshoot/platforms/kmp.md index cb3bbc1..43041e3 100644 --- a/revenuecat/skills/revenuecat-troubleshoot/platforms/kmp.md +++ b/revenuecat/skills/revenuecat-troubleshoot/platforms/kmp.md @@ -21,7 +21,7 @@ import com.revenuecat.purchases.kmp.Purchases Purchases.logLevel = LogLevel.DEBUG ``` -This flows to both native SDKs. The log output on each target matches the native SDK output, so follow the native platform file for the expected log lines. +This flows to both native SDKs, and each target logs what its native SDK logs. At configure time, both print a line containing `SDK Version - `, where the version is the native SDK's, not the purchases-kmp version. ## API key confusion @@ -34,26 +34,16 @@ Confirm the platform specific entry points pass the matching key: ## Version mismatch between KMP and native SDKs -`purchases-kmp-core` tracks native SDK versions but does not always update in lockstep. If the KMP version is newer than the native CocoaPods or Maven artifact it bridges to, you can hit missing symbol errors at link time or runtime. - -Check the installed `purchases-kmp-core` version in your `build.gradle.kts` and cross reference the native versions it depends on. If in doubt, pin the native iOS and Android SDKs explicitly to known good versions. +Since 3.0.0, `purchases-kmp` bundles `purchases-ios` and depends on a specific `purchases-android` version (3.10.1 uses 10.22.1). Do not add the native SDKs to the app yourself, so the versions `purchases-kmp` was built against stay in use. The exception is the Amazon Appstore: since 3.0.0 you add `purchases-store-amazon` yourself, so pin it to that same `purchases-android` version. If another dependency pulls in a different `purchases-android`, check the resolved version with `./gradlew ::dependencies` on the module that applies the Android plugin (often `:composeApp` or `:androidApp`). ## iOS framework linking issues -If the Kotlin framework builds but the iOS app fails to link `RevenueCat` symbols, the CocoaPods integration may not be pulling the bridge pod. Rebuild with: - -```bash -cd iosApp -pod deintegrate -pod install -``` - -Then clean the Xcode build folder (Product โ†’ Clean Build Folder) before running again. +Since 3.0.0, `purchases-kmp` brings the iOS SDK itself. If linking fails after an upgrade from 2.x, remove the old `PurchasesHybridCommon` and `PurchasesHybridCommonUI` pods or Swift packages ([3.0.0 migration guide](https://github.com/RevenueCat/purchases-kmp/blob/main/migrations/3.0.0-MIGRATION.md)), and run `pod install` again if you use CocoaPods. If the project still does not compile, set `isStatic = true` on the iOS framework binaries. Then clean the Xcode build folder (Product โ†’ Clean Build Folder) before running again. -## Android Context missing +## Android context -If the Android actual of `PurchasesConfiguration` requires a `Context` and the shared code does not supply one, the app crashes at configure time. Construct the configuration on the Android side where `this` (the `Application`) is in scope, then call into shared initialization for anything that does not need `Context`. +`PurchasesConfiguration.Builder` takes no `Context`. `purchases-kmp` gets the Android `Application` through AndroidX App Startup. If the SDK fails on Android at configure time, check that the merged manifest still contains `androidx.startup.InitializationProvider`. Apps that strip it, for example to drop WorkManager's initializer, should remove only that initializer. ## Verify -Reproduce on the target that was failing. Confirm the log banner and the success log for the scenario. Since `purchases-kmp` delegates to the native SDK, the RevenueCat dashboard should reflect the transaction exactly as it would for a native app. +Reproduce on the target that was failing. Confirm the `SDK Version - ` configure line and the success log for the scenario. Since `purchases-kmp` delegates to the native SDK, the RevenueCat dashboard should reflect the transaction exactly as it would for a native app. diff --git a/revenuecat/skills/revenuecat-troubleshoot/platforms/react-native.md b/revenuecat/skills/revenuecat-troubleshoot/platforms/react-native.md index cfdc90f..58ce511 100644 --- a/revenuecat/skills/revenuecat-troubleshoot/platforms/react-native.md +++ b/revenuecat/skills/revenuecat-troubleshoot/platforms/react-native.md @@ -73,7 +73,7 @@ A common report: one platform works, the other returns `INVALID_CREDENTIALS`. Ca - Await `Purchases.getCustomerInfo()` after the purchase promise resolves to get the fresh state. - The product must be attached to an entitlement in the dashboard. -- On Android, if your app is sideloaded instead of installed from Play Internal Testing, purchases will fail or behave inconsistently. Use `eas build --profile preview` or a signed AAB installed via the Play opt-in link. +- On Android, a sideloaded build makes test purchases only for a license tester who has opted in to the testing track. Other accounts either cannot buy or are charged real money, even when the build comes from the track, so add test accounts as license testers. ## iOS pod install forgotten