fix(android): extract basePlanId from ProductDetails for subs#39
fix(android): extract basePlanId from ProductDetails for subs#39
Conversation
Purchase objects don't expose basePlanId directly. Extract it from ProductDetails cache and populate basePlanIdAndroid/currentPlanId fields in getActiveSubscriptions() and getAvailablePurchases(). - Cache basePlanId during purchase flow - Extract from ProductDetails in onPurchasesUpdated - Update both Play and Horizon flavors
WalkthroughPropagates optional basePlanId/currentPlanId and purchaseTokenAndroid through Horizon and Play billing converters, updates OpenIapModule to extract/cache basePlanId from ProductDetails and pass it into conversions, replaces Android Log calls with OpenIapLog, and adds/duplicates debugging docs entries. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client
participant OpenIap as OpenIapModule
participant Cache as offerTokenToBasePlanIdCache
participant Product as ProductDetails cache
participant Converters as BillingConverters
participant Purchase as PurchaseAndroid
Client->>OpenIap: onPurchasesUpdated / request active subscriptions
OpenIap->>Product: lookup ProductDetails (productId)
Product-->>OpenIap: ProductDetails (with subscriptionOfferDetails)
OpenIap->>OpenIap: extract basePlanId from first offer (warn if multiple)
OpenIap->>Cache: cache[offerToken] = basePlanId
OpenIap->>Converters: toPurchase(purchase, basePlanId)
Converters->>Purchase: create PurchaseAndroid(currentPlanId=basePlanId, purchaseTokenAndroid=...)
Purchase-->>OpenIap: enriched PurchaseAndroid
OpenIap-->>Client: ActiveSubscription (basePlanId/currentPlanId populated)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20–30 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/google/openiap/src/horizon/java/dev/hyo/openiap/utils/BillingConverters.kt (1)
98-148: Add missingBillingPurchase.toActiveSubscription()function to Play flavor for parity with Horizon.The Play flavor is missing the intermediate conversion function that exists in the Horizon flavor. While Play has
BillingPurchase.toPurchase(productType: String, basePlanId: String?)with the basePlanId parameter, it lacksBillingPurchase.toActiveSubscription(). This creates an asymmetry in the two billing implementations—Horizon provides three conversion functions while Play provides only two.Add the equivalent conversion in
packages/google/openiap/src/play/java/dev/hyo/openiap/utils/BillingConverters.kt:fun BillingPurchase.toActiveSubscription(): ActiveSubscription = ActiveSubscription( autoRenewingAndroid = isAutoRenewing, basePlanIdAndroid = null, currentPlanId = null, isActive = true, productId = products.firstOrNull().orEmpty(), purchaseToken = purchaseToken, purchaseTokenAndroid = purchaseToken, transactionDate = purchaseTime.toDouble(), transactionId = orderId ?: purchaseToken )
🧹 Nitpick comments (1)
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt (1)
564-570: Caching logic is correct but cache is not utilized.The basePlanId is correctly cached here during the purchase flow. However, as noted in the comment for lines 106-108, this cache cannot be queried in
onPurchasesUpdatedbecauseBillingPurchasedoes not expose theofferToken. Consider restructuring the cache to usepurchaseTokenas the key instead, or removing this caching logic if it serves no purpose.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
packages/google/openiap/src/horizon/java/dev/hyo/openiap/utils/BillingConverters.kt(2 hunks)packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt(4 hunks)packages/google/openiap/src/play/java/dev/hyo/openiap/utils/BillingConverters.kt(2 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().
📚 Learning: 2025-10-18T05:46:51.596Z
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().
Applied to files:
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.ktpackages/google/openiap/src/play/java/dev/hyo/openiap/utils/BillingConverters.ktpackages/google/openiap/src/horizon/java/dev/hyo/openiap/utils/BillingConverters.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/**/*.kt : In Android-only package, do not add Android suffix to function names (e.g., acknowledgePurchase, not acknowledgePurchaseAndroid)
Applied to files:
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.ktpackages/google/openiap/src/play/java/dev/hyo/openiap/utils/BillingConverters.ktpackages/google/openiap/src/horizon/java/dev/hyo/openiap/utils/BillingConverters.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/**/*.kt : Only use Android suffix for types that are part of a cross-platform API (e.g., ProductAndroid, PurchaseAndroid)
Applied to files:
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.ktpackages/google/openiap/src/play/java/dev/hyo/openiap/utils/BillingConverters.ktpackages/google/openiap/src/horizon/java/dev/hyo/openiap/utils/BillingConverters.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/java/dev/hyo/openiap/utils/**/*.kt : Place reusable Kotlin helpers in openiap/src/main/java/dev/hyo/openiap/utils/
Applied to files:
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/Types.kt : Do not edit generated file openiap/src/main/Types.kt in Android package
Applied to files:
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.ktpackages/google/openiap/src/play/java/dev/hyo/openiap/utils/BillingConverters.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/apple/Sources/Models/**/*.swift : Keep official OpenIAP types in Sources/Models/ matching openiap.dev/docs/types naming (e.g., Product.swift, Purchase.swift, ActiveSubscription.swift)
Applied to files:
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.ktpackages/google/openiap/src/play/java/dev/hyo/openiap/utils/BillingConverters.kt
🧬 Code graph analysis (2)
packages/google/openiap/src/play/java/dev/hyo/openiap/utils/BillingConverters.kt (1)
packages/gql/src/generated/types.ts (1)
PurchaseAndroid(424-444)
packages/google/openiap/src/horizon/java/dev/hyo/openiap/utils/BillingConverters.kt (1)
packages/gql/src/generated/types.ts (2)
PurchaseAndroid(424-444)ActiveSubscription(6-32)
🔇 Additional comments (5)
packages/google/openiap/src/horizon/java/dev/hyo/openiap/utils/BillingConverters.kt (2)
138-148: LGTM! Field mappings are correct and consistent.The mappings properly align
currentPlanIdto bothbasePlanIdAndroidandcurrentPlanIdin the resultingActiveSubscription, which is correct per the type definitions (both should contain the basePlanId on Android). The addition ofpurchaseTokenAndroidalongsidepurchaseTokenensures all required fields for subscription management are populated.
126-136: ****The review references
HorizonPurchase.toActiveSubscription()as part of an active dual conversion strategy, but this function is never called in the codebase. The actual conversion flow is:HorizonPurchase→toPurchase()→PurchaseAndroid→toActiveSubscription()→ActiveSubscription. TheHorizonPurchase.toActiveSubscription()function (lines 126-136) is dead code and not part of any active conversion path.Likely an incorrect or invalid review comment.
packages/google/openiap/src/play/java/dev/hyo/openiap/utils/BillingConverters.kt (3)
100-100: LGTM! Signature updated to accept optional basePlanId.The addition of the optional
basePlanIdparameter allows callers to provide base plan information when available, which is then propagated toPurchaseAndroid.currentPlanId.
134-144: Consistent mapping from currentPlanId to basePlanIdAndroid.The mapping correctly propagates
currentPlanIdto bothbasePlanIdAndroidandcurrentPlanIdinActiveSubscription. The addition ofpurchaseTokenAndroidon line 141 appears unrelated to the basePlanId feature but aligns the Android-specific field with the commonpurchaseTokenfield.
104-104: The mapping is correct; the review comment is based on a misunderstanding of the domain model.basePlanId is the standard Google Play identifier for a subscription base plan, and currentPlanId is not a standard Google Play Developer API field. The codebase uses
currentPlanIdas a unified domain model field that represents the currently active plan identifier across platforms:
- Android:
currentPlanIdis populated from Google'sbasePlanId(e.g., "premium")- iOS:
currentPlanIdis populated from theproductIdThe documentation explicitly clarifies this semantic: "On Android, this is the basePlanId (e.g., 'premium', 'premium-year')." The naming difference is intentional for cross-platform consistency, not a semantic distinction. The mapping at line 104 is correct.
packages/google/openiap/src/horizon/java/dev/hyo/openiap/utils/BillingConverters.kt
Show resolved
Hide resolved
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt (1)
215-232: basePlanId extraction logic is duplicated.This basePlanId extraction logic (with warning for multiple offers) is identical to:
- Lines 886-890 in this file (onPurchasesUpdated)
- Lines 719-723 in the Horizon module
Refer to the review comment on the Horizon module for a suggested shared utility function to eliminate this duplication.
🧹 Nitpick comments (1)
packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt (1)
717-730: basePlanId limitation is now documented; consider extracting duplicate logic.The code appropriately warns when multiple offers exist and acknowledges the
basePlanIdmay be inaccurate. This limitation is now documented in the new "Multiple Subscription Offers" section added to the documentation.However, this exact logic (extracting basePlanId with a warning for multiple offers) is duplicated in the Play module at lines 219-223 and 886-890. Consider extracting this to a shared utility function to maintain consistency.
Example utility function:
// In packages/google/openiap/src/main/java/dev/hyo/openiap/utils/SubscriptionUtils.kt fun extractBasePlanId( productDetails: ProductDetails?, productId: String?, tag: String ): String? { val offers = productDetails?.subscriptionOfferDetails.orEmpty() if (offers.size > 1) { OpenIapLog.w( "Multiple offers (${offers.size}) found for ${productId}, using first basePlanId (may be inaccurate)", tag ) } return offers.firstOrNull()?.basePlanId }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
packages/docs/src/components/SearchModal.tsx(1 hunks)packages/docs/src/pages/docs/apis.tsx(1 hunks)packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.kt(1 hunks)packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt(5 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
packages/docs/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
packages/docs/src/**/*.{ts,tsx}: Platform suffixes in function names for documentation site examples: iOS-only functions end with IOS; Android-only functions end with Android; cross-platform functions have no suffix
Use kebab-case for search modal IDs in docs (e.g., id: 'request-products')
Trigger opening modals from pages/components by calling openAuthModal from the signals module
Components must not overflow parent boundaries: ensure children fit; use overflow-hidden on parents as needed; apply break-words for long text; use whitespace-nowrap for nav items
Delete unused components, functions, imports; avoid commented-out code; remove unused variables/parameters
Wrap any Promise-returning function used in a void context (e.g., React event handlers) with the void operator
Files:
packages/docs/src/components/SearchModal.tsxpackages/docs/src/pages/docs/apis.tsx
packages/docs/src/**/*.{md,mdx,ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Respect deprecations in documentation: replace buy-promoted-product-ios with requestPurchaseOnPromotedProductIOS; requestProducts with fetchProducts; get-storefront-ios with getStorefront
Files:
packages/docs/src/components/SearchModal.tsxpackages/docs/src/pages/docs/apis.tsx
packages/docs/src/components/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Place reusable shared components under src/components/; co-locate components used only once with their parent
Files:
packages/docs/src/components/SearchModal.tsx
packages/docs/src/components/**
📄 CodeRabbit inference engine (CLAUDE.md)
Use scoped component folder pattern when a component has internal-only subcomponents (e.g., src/components/AuthModal/ with index.tsx and internal files)
Files:
packages/docs/src/components/SearchModal.tsx
🧠 Learnings (10)
📓 Common learnings
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/**/*.kt : In Android-only package, do not add Android suffix to function names (e.g., acknowledgePurchase, not acknowledgePurchaseAndroid)
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/docs/src/**/*.{ts,tsx} : Use kebab-case for search modal IDs in docs (e.g., id: 'request-products')
Applied to files:
packages/docs/src/components/SearchModal.tsx
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/docs/src/**/*.{ts,tsx} : Trigger opening modals from pages/components by calling openAuthModal from the signals module
Applied to files:
packages/docs/src/components/SearchModal.tsx
📚 Learning: 2025-10-18T05:46:51.596Z
Learnt from: hyochan
Repo: hyodotdev/openiap PR: 17
File: packages/google/openiap/src/main/java/dev/hyo/openiap/store/OpenIapStore.kt:620-629
Timestamp: 2025-10-18T05:46:51.596Z
Learning: In packages/google/openiap/src/main/java/**/*.kt (Android-only package): DO NOT add Android suffix to function names, even for Android-specific APIs. Exception: Only use Android suffix for cross-platform API types (e.g., ProductAndroid, PurchaseAndroid) that contrast with iOS types. Examples of correct naming: isHorizonEnvironment(context: Context), buildModule(context: Context), acknowledgePurchase(), consumePurchase().
Applied to files:
packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.ktpackages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/**/*.kt : In Android-only package, do not add Android suffix to function names (e.g., acknowledgePurchase, not acknowledgePurchaseAndroid)
Applied to files:
packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.ktpackages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/**/*.kt : Only use Android suffix for types that are part of a cross-platform API (e.g., ProductAndroid, PurchaseAndroid)
Applied to files:
packages/google/openiap/src/horizon/java/dev/hyo/openiap/OpenIapModule.ktpackages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/docs/src/**/*.{ts,tsx} : Platform suffixes in function names for documentation site examples: iOS-only functions end with IOS; Android-only functions end with Android; cross-platform functions have no suffix
Applied to files:
packages/docs/src/pages/docs/apis.tsx
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/docs/src/**/*.{md,mdx,ts,tsx} : Respect deprecations in documentation: replace buy-promoted-product-ios with requestPurchaseOnPromotedProductIOS; requestProducts with fetchProducts; get-storefront-ios with getStorefront
Applied to files:
packages/docs/src/pages/docs/apis.tsx
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/docs/src/**/*.{ts,tsx} : Delete unused components, functions, imports; avoid commented-out code; remove unused variables/parameters
Applied to files:
packages/docs/src/pages/docs/apis.tsx
📚 Learning: 2025-10-18T05:54:54.802Z
Learnt from: CR
Repo: hyodotdev/openiap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-18T05:54:54.802Z
Learning: Applies to packages/google/openiap/src/main/java/dev/hyo/openiap/utils/**/*.kt : Place reusable Kotlin helpers in openiap/src/main/java/dev/hyo/openiap/utils/
Applied to files:
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Test Android
🔇 Additional comments (3)
packages/docs/src/components/SearchModal.tsx (1)
409-437: LGTM! Search entries align with new documentation.The three new API documentation items follow the kebab-case convention for IDs and correctly reference the new Debugging & Logging section added to the documentation.
packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt (1)
859-864: Good migration to OpenIapLog for consistent logging.The replacement of Android Log calls with OpenIapLog provides consistent, controlled logging across the module. This aligns with the PR's goal of enhancing debugging capabilities.
Also applies to: 907-907, 1079-1079
packages/docs/src/pages/docs/apis.tsx (1)
1193-1316: No duplication exists—the review comment concern is unfounded.The verification search found only a single instance of "Debugging & Logging" at lines 1194–1195. The original review comment's concern that this section is "added twice" is not supported by the codebase. The section appears once in the file and does not require correction.
Likely an incorrect or invalid review comment.
Purchase objects don't expose basePlanId directly. Extract it from ProductDetails cache and populate basePlanIdAndroid/currentPlanId fields in getActiveSubscriptions() and getAvailablePurchases().
Summary by CodeRabbit
Bug Fixes
Refactor
Chores
Documentation