fix(libraries): preserve product metadata bridges - #219
Conversation
Propagate iOS pricing terms and subscription metadata through the React Native Nitro bridge, preserve generated product payloads in Godot and KMP iOS paths, and parse the corresponding Flutter product fields with focused coverage.
|
Warning Review limit reached
Next review available in: 8 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (17)
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughPropagates standardized product, subscription, offer, status, pricing, serialization, query, and generated-model metadata across Flutter, React Native, KMP, Apple, Google, Horizon, Godot, and GraphQL integrations. ChangesFlutter product and offer decoding
React Native bridge metadata
KMP and platform product flows
Apple, Horizon, Amazon, Godot, and generated model updates
CI workflow updates
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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.
Code Review
This pull request adds support for several new cross-platform in-app purchase properties, including pricingTermsIOS, subscriptionInfoIOS, discountOffers, productStatusAndroid, installmentPlanDetails, and debugDescription across Flutter, React Native, Godot, and Kotlin Multiplatform (KMP) implementations. It also standardizes subscription offer parsing and simplifies serialization logic. The reviewer suggests improving the robustness of the generic list parser _parseGeneratedList in Flutter; currently, if a single item fails to parse, the entire list is discarded. Skipping the malformed item and continuing to parse the rest of the list would prevent complete parsing failures.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Code Review
This pull request standardizes and expands in-app purchase data models across Flutter, Godot, Kotlin Multiplatform, and React Native wrappers. It adds support for parsing new fields such as pricingTermsIOS, subscriptionInfoIOS, discountOffers, productStatusAndroid, and installmentPlanDetails, along with corresponding unit tests. Feedback was provided on the Flutter helper to safely parse installment plan details using _toInt instead of relying on direct type casts, which could cause runtime crashes if the platform channel returns unexpected numeric types.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Keep parsing valid generated product metadata list entries when one malformed entry is present, and cover the mixed-list case in helper tests.
|
Addressed Gemini parser robustness feedback in 1561c22. Changes:
|
Avoid generated int casts for native installment plan details and cover string/double platform-channel numeric values in helper tests.
There was a problem hiding this comment.
Code Review
This pull request introduces support for new subscription-related fields (such as pricingTermsIOS, subscriptionInfoIOS, discountOffers, productStatusAndroid, and installmentPlanDetails) across multiple platform libraries, including Flutter, Godot, Kotlin Multiplatform, and React Native. The review feedback focuses on improving the robustness of the parsing logic. Specifically, it suggests adding type guards and defensive checks in the Flutter helper functions to prevent crashes or redundant parsing, and checking for empty collections before serializing them in the iOS React Native helper to avoid passing empty JSON strings over the bridge.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
I am having trouble creating individual review comments. Click here to see my feedback.
libraries/flutter_inapp_purchase/lib/helpers.dart (576-594)
To make the list parsing more robust and defensive, we should check if the input value is already a List<T>, or if individual items in the list are already of type T. This prevents unnecessary parsing attempts and potential failures if the helper is called with already-processed or mock data.
List<T>? _parseGeneratedList<T>(
dynamic value,
T Function(Map<String, dynamic>) fromJson,
) {
if (value is List<T>) return value;
final list = _parseNativeList(value);
if (list == null) return null;
final parsed = <T>[];
for (final item in list) {
if (item is T) {
parsed.add(item);
continue;
}
final map = normalizeDynamicMap(item);
if (map == null) continue;
try {
parsed.add(fromJson(map));
} catch (_) {
continue;
}
}
return parsed.isEmpty ? null : parsed;
}libraries/flutter_inapp_purchase/lib/helpers.dart (610-617)
If value is already of type gentype.ProductStatusAndroid, calling value.toString() will return a string representation like "ProductStatusAndroid.Ok", which will fail to parse in fromJson and return null. We should add a type guard to return the value directly if it is already the correct enum type.
gentype.ProductStatusAndroid? _parseProductStatusAndroid(dynamic value) {
if (value == null) return null;
if (value is gentype.ProductStatusAndroid) return value;
try {
return gentype.ProductStatusAndroid.fromJson(value.toString());
} catch (_) {
return null;
}
}
libraries/flutter_inapp_purchase/lib/helpers.dart (659-669)
If gentype.InstallmentPlanDetailsAndroid.fromJson throws an exception due to unexpected or malformed data from the native layer, it will crash the entire product list parsing flow. Wrapping this call in a try-catch block ensures that the app can gracefully degrade by setting installmentPlanDetails to null instead of crashing.
final installmentPlanDetailsMap = normalizeDynamicMap(
e['installmentPlanDetails'],
);
gentype.InstallmentPlanDetailsAndroid? installmentPlanDetails;
if (installmentPlanDetailsMap != null) {
try {
installmentPlanDetails = gentype.InstallmentPlanDetailsAndroid.fromJson(
installmentPlanDetailsMap,
);
} catch (_) {
installmentPlanDetails = null;
}
}
return gentype.ProductSubscriptionAndroidOfferDetails(
basePlanId: e['basePlanId'] as String? ?? '',
installmentPlanDetails: installmentPlanDetails,
libraries/react-native-iap/ios/RnIapHelper.swift (148-164)
To avoid serializing empty arrays or dictionaries and passing unnecessary empty JSON strings ("[]" or "{}") over the bridge, we should check that the collections are not empty before serializing them. This is consistent with how subscriptionOffers and discountOffers are handled.
var pricingTermsIOS: Variant_NullType_String? = nil
if let pricingTermsArray = dictionary["pricingTermsIOS"] as? [[String: Any]], !pricingTermsArray.isEmpty {
if let json = serializeToJSON(pricingTermsArray) {
pricingTermsIOS = .second(json)
} else {
NSLog("⚠️ [RnIapHelper] Failed to serialize pricingTermsIOS")
}
}
var subscriptionInfoIOS: Variant_NullType_String? = nil
if let subscriptionInfo = dictionary["subscriptionInfoIOS"] as? [String: Any], !subscriptionInfo.isEmpty {
if let json = serializeToJSON(subscriptionInfo) {
subscriptionInfoIOS = .second(json)
} else {
NSLog("⚠️ [RnIapHelper] Failed to serialize subscriptionInfoIOS")
}
}
There was a problem hiding this comment.
Code Review
This pull request introduces updates across multiple platforms (Flutter, Godot, KMP, and React Native) to support new in-app purchase fields, including pricingTermsIOS, subscriptionInfoIOS, discountOffers, productStatusAndroid, installmentPlanDetails, and debugDescription. It also refactors serialization logic and updates corresponding unit tests. Regarding the feedback, a bug was identified in the Flutter helper method _parseStandardizedSubscriptionOffers where legacy iOS subscription offers in JSON string format fail to parse because the raw string is passed to the fallback parser instead of the decoded list.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Handle already-typed Flutter metadata values, preserve defensive installment parsing, and avoid serializing empty iOS metadata collections over the React Native bridge.
|
Addressed the latest Gemini top-level review feedback in 48a3eca. Changes:
Verification:
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt (1)
1257-1279: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winPreserve legacy
offersduring iOS model decoding.ProductIOS.fromJsonandProductSubscriptionIOS.fromJsononly readsubscriptionOffers, andOpenIapSerialization.encodealways tags payloads with__typename, so the generated decode path will usually succeed and skip the manualmap["subscriptionOffers"] ?: map["offers"]recovery. That drops offer metadata for older payloads inconvertAnyListToProducts,convertAnyListToProductSubscriptions, andconvertAnyListToProductOrSubscriptions; merge the legacy key after decoding, not only on failure.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt` around lines 1257 - 1279, Preserve legacy offers when decoding iOS products/subscriptions: the current recovery in convertAnyListToProducts, convertAnyListToProductSubscriptions, and convertAnyListToProductOrSubscriptions only applies map["subscriptionOffers"] ?: map["offers"] when Product.fromJson/ProductSubscription.fromJson fails, but decode usually succeeds because OpenIapSerialization.encode adds __typename. Update the decode flow in InAppPurchaseIOS to merge the legacy offers key into the decoded result after successful parsing as well, using the existing Product.fromJson and ProductSubscription.fromJson paths so older payloads keep their offer metadata.Source: Coding guidelines
🧹 Nitpick comments (3)
libraries/react-native-iap/src/__tests__/utils/type-bridge.test.ts (1)
105-180: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winOptional: add a negative-path test for pricingTermsIOS/subscriptionInfoIOS.
The existing suite has an "invalid JSON" test for
subscriptionOffers(line 287) but none forpricingTermsIOS/subscriptionInfoIOS. Adding one would cover thecatchfallback-to-nullbranches for these new fields, matching the "for error paths ... catch blocks" test-coverage expectation for this package.As per path instructions, "When writing tests, focus coverage on: ... both 'on PlatformException catch' and generic 'catch' blocks ... and edge cases (null values, empty strings, missing fields)".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/react-native-iap/src/__tests__/utils/type-bridge.test.ts` around lines 105 - 180, Add a negative-path test in type-bridge.test for convertNitroProductToProduct that feeds invalid JSON into pricingTermsIOS and subscriptionInfoIOS, then assert both fields fall back to null. This should mirror the existing subscriptionOffers invalid-JSON coverage and exercise the catch branches in the NitroProduct-to-Product conversion logic.Source: Path instructions
libraries/react-native-iap/src/utils/type-bridge.ts (1)
275-330: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider extracting the repeated JSON-parse-with-fallback pattern.
The block now repeats the same
if (value) { try { JSON.parse(...) } catch { null } } else { null }shape seven times across this function (discountsIOS, pricingTermsIOS, subscriptionInfoIOS, subscriptionOffers ×2, discountOffers ×2). A small helper likeparseJsonField<T>(value: string | null | undefined): T | nullwould remove the duplication and make future additions (e.g. more StoreKit metadata fields) trivial.♻️ Example helper
+function parseJsonField<T>(value: string | null | undefined): T | null { + if (!value) return null; + try { + return JSON.parse(value) as T; + } catch { + return null; + } +}Then each block collapses to e.g.
iosProduct.pricingTermsIOS = parseJsonField(nitroProduct.pricingTermsIOS);Also applies to: 347-369
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/react-native-iap/src/utils/type-bridge.ts` around lines 275 - 330, The iOS product mapping in type-bridge.ts repeats the same JSON parse/fallback logic for multiple Nitro fields, making the function hard to maintain. Extract that pattern into a reusable helper such as parseJsonField<T>(value) and use it in the existing iosProduct assignment path for discountsIOS, pricingTermsIOS, subscriptionInfoIOS, subscriptionOffers, and discountOffers so each field collapses to a single assignment.libraries/flutter_inapp_purchase/lib/helpers.dart (1)
583-601: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value
_parseGeneratedListdiscards the whole list on one bad item.A
nullmap is skipped (continue), but afromJsonthrow returnsnullfor the entire list. For fields without a fallback (pricingTermsIOS,discountOffers), one malformed entry silently drops all valid entries. If per-item resilience is desired,catchandcontinueinstead of returning. Flagging as optional since all-or-nothing may be intentional here.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/flutter_inapp_purchase/lib/helpers.dart` around lines 583 - 601, _update `_parseGeneratedList` in `helpers.dart` so one malformed entry does not null out the entire result._ The current `fromJson` catch in `_parseGeneratedList<T>` returns `null`, which discards previously parsed valid items for lists like `pricingTermsIOS` and `discountOffers`. Change the error handling to skip bad entries and continue accumulating parsed items, keeping the existing `normalizeDynamicMap` flow and only returning `null` when the source list itself is unavailable or no valid items were parsed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@libraries/flutter_inapp_purchase/lib/helpers.dart`:
- Around line 610-614: The legacy subscription-offers fallback in
_parseStandardizedSubscriptionOffers is dropping JSON-encoded String payloads
because _parseSubscriptionOffersIOS only handles List input. Update the parsing
path so that when _parseGeneratedList fails, the fallback first decodes string
values and then routes them through the existing SubscriptionOffer conversion
logic, ensuring both list and string payloads are accepted.
---
Outside diff comments:
In
`@libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt`:
- Around line 1257-1279: Preserve legacy offers when decoding iOS
products/subscriptions: the current recovery in convertAnyListToProducts,
convertAnyListToProductSubscriptions, and convertAnyListToProductOrSubscriptions
only applies map["subscriptionOffers"] ?: map["offers"] when
Product.fromJson/ProductSubscription.fromJson fails, but decode usually succeeds
because OpenIapSerialization.encode adds __typename. Update the decode flow in
InAppPurchaseIOS to merge the legacy offers key into the decoded result after
successful parsing as well, using the existing Product.fromJson and
ProductSubscription.fromJson paths so older payloads keep their offer metadata.
---
Nitpick comments:
In `@libraries/flutter_inapp_purchase/lib/helpers.dart`:
- Around line 583-601: _update `_parseGeneratedList` in `helpers.dart` so one
malformed entry does not null out the entire result._ The current `fromJson`
catch in `_parseGeneratedList<T>` returns `null`, which discards previously
parsed valid items for lists like `pricingTermsIOS` and `discountOffers`. Change
the error handling to skip bad entries and continue accumulating parsed items,
keeping the existing `normalizeDynamicMap` flow and only returning `null` when
the source list itself is unavailable or no valid items were parsed.
In `@libraries/react-native-iap/src/__tests__/utils/type-bridge.test.ts`:
- Around line 105-180: Add a negative-path test in type-bridge.test for
convertNitroProductToProduct that feeds invalid JSON into pricingTermsIOS and
subscriptionInfoIOS, then assert both fields fall back to null. This should
mirror the existing subscriptionOffers invalid-JSON coverage and exercise the
catch branches in the NitroProduct-to-Product conversion logic.
In `@libraries/react-native-iap/src/utils/type-bridge.ts`:
- Around line 275-330: The iOS product mapping in type-bridge.ts repeats the
same JSON parse/fallback logic for multiple Nitro fields, making the function
hard to maintain. Extract that pattern into a reusable helper such as
parseJsonField<T>(value) and use it in the existing iosProduct assignment path
for discountsIOS, pricingTermsIOS, subscriptionInfoIOS, subscriptionOffers, and
discountOffers so each field collapses to a single assignment.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: bf922e8f-b3d8-4ed5-9117-e398d32844e0
📒 Files selected for processing (9)
libraries/flutter_inapp_purchase/lib/helpers.dartlibraries/flutter_inapp_purchase/test/helpers_unit_test.dartlibraries/godot-iap/ios-gdextension/Sources/GodotIap/GodotIap.swiftlibraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.ktlibraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.ktlibraries/react-native-iap/ios/RnIapHelper.swiftlibraries/react-native-iap/src/__tests__/utils/type-bridge.test.tslibraries/react-native-iap/src/specs/RnIap.nitro.tslibraries/react-native-iap/src/utils/type-bridge.ts
Pass decoded subscription offer lists to both generated and legacy iOS parsers, and cover legacy JSON string offers that require fallback parsing.
There was a problem hiding this comment.
Code Review
This pull request introduces support for several new iOS and Android in-app purchase fields across multiple platform libraries (Flutter, React Native, Godot, and KMP). Specifically, it adds parsing and serialization for pricingTermsIOS, subscriptionInfoIOS, discountOffers, productStatusAndroid, and installmentPlanDetails, along with corresponding unit tests. Feedback on the changes suggests falling back to the "subscription" key in RnIapHelper.swift when "subscriptionInfoIOS" is missing to ensure backward compatibility and parity with Flutter, as well as adding runtime type validation when parsing pricingTermsIOS and subscriptionInfoIOS in the React Native type bridge.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Fall back to legacy subscription metadata keys, validate parsed iOS metadata JSON shapes, and cover invalid-shape handling in type-bridge tests.
There was a problem hiding this comment.
Code Review
This pull request introduces support for several new in-app purchase properties across multiple platform libraries, including Flutter, React Native, Godot, and KMP. Specifically, it adds parsing, serialization, and mapping for properties such as pricingTermsIOS, subscriptionInfoIOS, discountOffers, productStatusAndroid, installmentPlanDetailsAndroid, and debugDescription. Additionally, it simplifies serialization in the Godot Swift implementation using OpenIapSerialization and updates the corresponding unit tests to verify the new parsing logic. I have no feedback to provide as there are no review comments.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Code Review
This pull request introduces several updates across multiple packages to support new iOS and Android product metadata fields, specifically pricingTermsIOS, subscriptionInfoIOS, discountOffers, productStatusAndroid, and installmentPlanDetails. The changes include adding parsing helpers and unit tests in the Flutter package, refactoring Swift serialization in the Godot GDExtension, adding JSON-based parsing fallbacks in the KMP package, and updating the React Native Nitro bridge and its tests to handle these new fields. No review comments were provided, so there is no additional feedback.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Merge legacy offers into successfully decoded iOS product payloads and extend React Native metadata error-path coverage.
|
Addressed CodeRabbit KMP legacy offers feedback in 0bc8fc8. Changes:
Verification:
|
There was a problem hiding this comment.
Code Review
This pull request adds support for parsing and serializing additional StoreKit and Google Play billing metadata across multiple platform libraries (Flutter, React Native, Godot, and KMP), including fields like pricingTermsIOS, subscriptionInfoIOS, discountOffers, productStatusAndroid, and debugDescription. Feedback on the changes highlights a critical stability issue in the React Native iOS helper where serializing native metadata dictionary values to JSON could cause runtime crashes if the objects contain non-JSON-serializable types; validating the objects using JSONSerialization.isValidJSONObject is recommended before serialization.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Code Review
This pull request enhances cross-platform in-app purchase metadata support across Flutter, Godot, KMP, and React Native libraries. It introduces parsing, serialization, and mapping for iOS-specific metadata (such as pricingTermsIOS and subscriptionInfoIOS), Android-specific details (like discountOffers, productStatusAndroid, and installmentPlanDetailsAndroid), and standardized subscriptionOffers. Additionally, it updates corresponding unit tests to validate these new metadata fields and ensure backward compatibility with legacy payloads. There are no review comments to address, and I have no feedback to provide.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt (1)
1448-1488: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
convertAnyToProductIOSskips the legacy-offer merge that its sibling conversion functions apply.
convertAnyListToProducts,convertAnyListToProductSubscriptions, andconvertAnyListToProductOrSubscriptionsall callmergeLegacySubscriptionOffers(it, map)after a successfulfromJsondecode so that legacysubscriptionOffers/offerspayloads are backfilled when the decoded model's own field is empty.convertAnyToProductIOS(used bygetPromotedProductIOS()) returns thefromJsonresult directly at Line 1456 without this merge step, so a promoted product with legacy-format offer data would silently losesubscriptionOfferswhere the other paths would not.🐛 Proposed fix
val map = dict.mapKeys { it.key.toString() } runCatching { ProductIOS.fromJson(map) }.getOrNull()?.let { - return it + return mergeLegacySubscriptionOffers(it, map) as ProductIOS }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt` around lines 1448 - 1488, `convertAnyToProductIOS` is missing the same legacy offer backfill used by the other conversion helpers. Update this function to apply `mergeLegacySubscriptionOffers` after a successful `ProductIOS.fromJson(map)` decode, using the same `map` data and preserving the decoded object when its `subscriptionOffers` is already populated; this keeps `getPromotedProductIOS()` consistent with `convertAnyListToProducts`, `convertAnyListToProductSubscriptions`, and `convertAnyListToProductOrSubscriptions`.
🧹 Nitpick comments (3)
libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt (1)
1398-1446: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winLegacy-offer extraction is duplicated across the three
mergeLegacySubscriptionOffersoverloads.Each overload independently recomputes
convertAnyListToSubscriptionOffers(map["subscriptionOffers"] ?: map["offers"])and re-checks emptiness. Extracting that into a single private helper (e.g.legacySubscriptionOffersOrNull(map)) called once by all three overloads would keep the three code paths in sync if the legacy-key fallback logic ever changes.♻️ Sketch
private fun legacySubscriptionOffersOrNull(map: Map<String, Any?>): List<SubscriptionOffer>? { val offers = convertAnyListToSubscriptionOffers(map["subscriptionOffers"] ?: map["offers"]) return offers.ifEmpty { null } }Then each
mergeLegacySubscriptionOffersoverload can short-circuit onlegacySubscriptionOffersOrNull(map) ?: return product.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt` around lines 1398 - 1446, The three mergeLegacySubscriptionOffers overloads duplicate the legacy-offer lookup and empty-check logic, so the fallback behavior can drift. Add a single private helper such as legacySubscriptionOffersOrNull(map) that calls convertAnyListToSubscriptionOffers with the subscriptionOffers/offers fallback once, returns null when empty, and have all mergeLegacySubscriptionOffers variants use it before deciding whether to copy ProductIOS or ProductSubscriptionIOS. Keep the ProductOrSubscription wrapper delegating to those shared overloads so all paths stay consistent.libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/Helper.kt (2)
486-490: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRedundant branch: both
paymentMode == PaymentMode.FreeTrialandelseresolve toIntroductory.The when-block's second and third branches both return
DiscountOfferType.Introductory, making the explicitFreeTrialcheck dead weight. If differentiation was intended here, this silently defaults instead; if not, the branch can be collapsed.♻️ Simplify
- val type = when { - offerId != null && offerId.isNotEmpty() -> DiscountOfferType.Promotional - paymentMode == PaymentMode.FreeTrial -> DiscountOfferType.Introductory - else -> DiscountOfferType.Introductory - } + val type = if (!offerId.isNullOrEmpty()) { + DiscountOfferType.Promotional + } else { + DiscountOfferType.Introductory + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/Helper.kt` around lines 486 - 490, The `when` block in `Helper.kt` has a redundant `paymentMode == PaymentMode.FreeTrial` branch because both it and the `else` branch return `DiscountOfferType.Introductory`. Simplify the logic in the `type` assignment by removing the dead-weight check or collapsing the branches so the `offerId` case remains explicit and the default behavior is clear. Use the existing `type` computation near `PaymentMode.FreeTrial` and `DiscountOfferType.Introductory` as the location to update.
362-457: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated extraction logic between
toOfferDetail()andtoDiscountOffer().Both extension functions independently rebuild
DiscountDisplayInfoAndroid,LimitedQuantityInfoAndroid,PreorderDetailsAndroid,RentalDetailsAndroid, andValidTimeWindowAndroidfrom the sameOneTimePurchaseOfferDetailsreceiver, with identical null-safe extraction. Extracting shared helpers (e.g.toPreorderDetailsAndroid(),toRentalDetailsAndroid(),toValidTimeWindowAndroid(),toLimitedQuantityInfoAndroid()) would remove ~40 lines of duplication and keep both mappers in sync if the source model changes.♻️ Sketch of shared helpers
private fun ProductDetails.OneTimePurchaseOfferDetails.toRentalDetailsAndroid(): RentalDetailsAndroid? = runCatching { rentalDetails }.getOrNull()?.let { details -> RentalDetailsAndroid( rentalPeriod = details.rentalPeriod, rentalExpirationPeriod = runCatching { details.rentalExpirationPeriod }.getOrNull() ) } private fun ProductDetails.OneTimePurchaseOfferDetails.toValidTimeWindowAndroid(): ValidTimeWindowAndroid? = runCatching { validTimeWindow }.getOrNull()?.let { window -> ValidTimeWindowAndroid( startTimeMillis = window.startTimeMillis.toString(), endTimeMillis = window.endTimeMillis.toString() ) } // ... use these from both toOfferDetail() and toDiscountOffer()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/Helper.kt` around lines 362 - 457, The two mappers, toOfferDetail() and toDiscountOffer(), duplicate the same null-safe conversion logic for shared fields from ProductDetails.OneTimePurchaseOfferDetails. Factor the repeated extraction into private helper methods such as toLimitedQuantityInfoAndroid(), toPreorderDetailsAndroid(), toRentalDetailsAndroid(), and toValidTimeWindowAndroid(), then reuse them from both functions so the mapping stays consistent and easier to maintain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In
`@libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt`:
- Around line 1448-1488: `convertAnyToProductIOS` is missing the same legacy
offer backfill used by the other conversion helpers. Update this function to
apply `mergeLegacySubscriptionOffers` after a successful
`ProductIOS.fromJson(map)` decode, using the same `map` data and preserving the
decoded object when its `subscriptionOffers` is already populated; this keeps
`getPromotedProductIOS()` consistent with `convertAnyListToProducts`,
`convertAnyListToProductSubscriptions`, and
`convertAnyListToProductOrSubscriptions`.
---
Nitpick comments:
In
`@libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/Helper.kt`:
- Around line 486-490: The `when` block in `Helper.kt` has a redundant
`paymentMode == PaymentMode.FreeTrial` branch because both it and the `else`
branch return `DiscountOfferType.Introductory`. Simplify the logic in the `type`
assignment by removing the dead-weight check or collapsing the branches so the
`offerId` case remains explicit and the default behavior is clear. Use the
existing `type` computation near `PaymentMode.FreeTrial` and
`DiscountOfferType.Introductory` as the location to update.
- Around line 362-457: The two mappers, toOfferDetail() and toDiscountOffer(),
duplicate the same null-safe conversion logic for shared fields from
ProductDetails.OneTimePurchaseOfferDetails. Factor the repeated extraction into
private helper methods such as toLimitedQuantityInfoAndroid(),
toPreorderDetailsAndroid(), toRentalDetailsAndroid(), and
toValidTimeWindowAndroid(), then reuse them from both functions so the mapping
stays consistent and easier to maintain.
In
`@libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt`:
- Around line 1398-1446: The three mergeLegacySubscriptionOffers overloads
duplicate the legacy-offer lookup and empty-check logic, so the fallback
behavior can drift. Add a single private helper such as
legacySubscriptionOffersOrNull(map) that calls
convertAnyListToSubscriptionOffers with the subscriptionOffers/offers fallback
once, returns null when empty, and have all mergeLegacySubscriptionOffers
variants use it before deciding whether to copy ProductIOS or
ProductSubscriptionIOS. Keep the ProductOrSubscription wrapper delegating to
those shared overloads so all paths stay consistent.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0260324e-0316-4038-ac01-0cb5c9175f13
📒 Files selected for processing (8)
libraries/flutter_inapp_purchase/lib/helpers.dartlibraries/flutter_inapp_purchase/test/helpers_unit_test.dartlibraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/Helper.ktlibraries/kmp-iap/library/src/commonMain/kotlin/io/github/hyochan/kmpiap/DslExtensions.ktlibraries/kmp-iap/library/src/commonTest/kotlin/io/github/hyochan/kmpiap/InAppPurchaseTest.ktlibraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.ktlibraries/react-native-iap/ios/RnIapHelper.swiftlibraries/react-native-iap/src/__tests__/utils/type-bridge.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
- libraries/react-native-iap/ios/RnIapHelper.swift
- libraries/react-native-iap/src/tests/utils/type-bridge.test.ts
- libraries/flutter_inapp_purchase/test/helpers_unit_test.dart
- libraries/flutter_inapp_purchase/lib/helpers.dart
|
Addressed CodeRabbit outside-diff KMP promoted product feedback in a3e341b. Changes:
Verification:
|
|
Warning Gemini encountered an error creating the review. You can try again by commenting |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt (2)
1275-1278: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winUse concrete iOS decoders before the manual fallback.
Product.fromJson/ProductSubscription.fromJsonrequire__typename; raw iOS maps without it fall through to the manual constructors, wheresubscriptionInfoIOSis still set tonull. That keeps list fetches from preserving the newly propagated metadata.Proposed fix
- runCatching { Product.fromJson(map) }.getOrNull()?.let { + runCatching { ProductIOS.fromJson(map) }.getOrNull()?.let { return@mapNotNull mergeLegacySubscriptionOffers(it, map) }- runCatching { ProductSubscription.fromJson(map) }.getOrNull()?.let { + runCatching { ProductSubscriptionIOS.fromJson(map) }.getOrNull()?.let { return@mapNotNull mergeLegacySubscriptionOffers(it, map) }Also applies to: 1323-1326
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt` around lines 1275 - 1278, The iOS product mapping in InAppPurchaseIOS should prefer the concrete decoders before falling back to manual construction. Update the Product.fromJson and ProductSubscription.fromJson paths to ensure __typename is present or otherwise decode through the iOS-specific model first, then pass the decoded object into mergeLegacySubscriptionOffers so subscriptionInfoIOS is preserved instead of becoming null. Apply the same fix in both affected mapNotNull blocks.
1402-1404: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winFall back to legacy
offerswhensubscriptionOffersis empty.
?:only handles null/missing values. IfsubscriptionOffersis[]while legacyoffershas entries, the merge returns the decoded object without restoring the legacy metadata.Proposed fix
+ private fun subscriptionOffersFrom(map: Map<String, Any?>): List<SubscriptionOffer> { + val standardized = convertAnyListToSubscriptionOffers(map["subscriptionOffers"]) + return standardized.ifEmpty { convertAnyListToSubscriptionOffers(map["offers"]) } + } + private fun mergeLegacySubscriptionOffers( product: Product, map: Map<String, Any?> ): Product { - val subscriptionOffers = convertAnyListToSubscriptionOffers( - map["subscriptionOffers"] ?: map["offers"] - ) + val subscriptionOffers = subscriptionOffersFrom(map) if (subscriptionOffers.isEmpty()) return productprivate fun mergeLegacySubscriptionOffers( subscription: ProductSubscription, map: Map<String, Any?> ): ProductSubscription { - val subscriptionOffers = convertAnyListToSubscriptionOffers( - map["subscriptionOffers"] ?: map["offers"] - ) + val subscriptionOffers = subscriptionOffersFrom(map) if (subscriptionOffers.isEmpty()) return subscriptionAlso applies to: 1421-1423
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt` around lines 1402 - 1404, The subscription offer mapping in InAppPurchaseIOS should fall back to legacy offers not only when subscriptionOffers is null or missing, but also when it decodes as empty. Update the conversion logic around convertAnyListToSubscriptionOffers so it checks the resulting list and, if it is empty, reuses map["offers"] to restore legacy metadata; apply the same fix in the corresponding second merge path around the same helper usage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In
`@libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt`:
- Around line 1275-1278: The iOS product mapping in InAppPurchaseIOS should
prefer the concrete decoders before falling back to manual construction. Update
the Product.fromJson and ProductSubscription.fromJson paths to ensure __typename
is present or otherwise decode through the iOS-specific model first, then pass
the decoded object into mergeLegacySubscriptionOffers so subscriptionInfoIOS is
preserved instead of becoming null. Apply the same fix in both affected
mapNotNull blocks.
- Around line 1402-1404: The subscription offer mapping in InAppPurchaseIOS
should fall back to legacy offers not only when subscriptionOffers is null or
missing, but also when it decodes as empty. Update the conversion logic around
convertAnyListToSubscriptionOffers so it checks the resulting list and, if it is
empty, reuses map["offers"] to restore legacy metadata; apply the same fix in
the corresponding second merge path around the same helper usage.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8e512d63-3c7c-4a4a-b34e-5ec8f46dfc23
📒 Files selected for processing (1)
libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt
There was a problem hiding this comment.
Code Review
This pull request updates multiple in-app purchase libraries (Flutter, Godot, KMP, and React Native) to support new subscription metadata fields, including pricing terms, discount offers, installment plan details, and product status, alongside standardized subscription offers. It also includes corresponding unit test updates to verify these parsing capabilities. The review feedback suggests optimizing performance by compiling the regular expressions for billing periods as top-level private constants instead of recreating them on every function call in both the Flutter and KMP helper files.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
Addressed CodeRabbit outside-diff KMP iOS decoder and legacy-offer fallback feedback in ddc94eb. Product list conversion now uses the concrete iOS decoders before manual fallback, and legacy offers are restored when subscriptionOffers is absent or empty. |
There was a problem hiding this comment.
Code Review
This pull request standardizes and expands subscription metadata support across Flutter, Godot, Kotlin Multiplatform (KMP), and React Native IAP libraries. It adds parsing, serialization, and bridging for several fields, including pricingTermsIOS, subscriptionInfoIOS, discountOffers, productStatusAndroid, and installmentPlanDetailsAndroid, supported by comprehensive unit tests. As there are no review comments, no further feedback is provided.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Code Review
This pull request updates the multiplatform IAP libraries (Flutter, Godot, KMP, React Native, iOS, and Android) to support standardized subscription offers, discount offers, pricing terms, and installment plan details, aligning with Google Play Billing Library 8.0+ and StoreKit 2. It also refactors the GDScript code generator to output typed arrays (Array[T]) for improved type safety in Godot. The review feedback highlights critical runtime safety issues on Android, specifically potential NoSuchMethodError crashes when accessing offerToken on older Billing Library versions, and suggests caching reflection Method lookups to optimize performance.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (5)
libraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt (1)
966-966: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftCover the native-to-Nitro assignments.
The supplied tests construct
NitroProductfixtures directly, so they cannot detect either assignment being removed or mapped incorrectly. Add afetchProducts-path test (or a focused mapper test) assertingpurchaseOptionIdanddebugDescriptionreach the Nitro result.As per coding guidelines, “When adding or modifying code, always write tests for new code paths with aim for full branch coverage.”
Also applies to: 1091-1091
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt` at line 966, The native-to-Nitro mapping for `purchaseOptionId` and `debugDescription` is untested. Add a focused mapper test or exercise the `fetchProducts` path using native product fixtures, then assert both fields are copied correctly into the resulting `NitroProduct`, including coverage for relevant branches.Source: Coding guidelines
.github/workflows/ci-godot-iap.yml (1)
31-33: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winSet
persist-credentials: falseon the checkout action.The checkout action persists a GitHub token in
.git/configby default. Subsequent Godot headless steps have filesystem access and could inadvertently expose it. The fix is a one-line addition.🔒 Proposed fix
- uses: actions/checkout@v7 with: fetch-depth: 1 + persist-credentials: false🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/ci-godot-iap.yml around lines 31 - 33, Configure the checkout step using actions/checkout@v7 to set persist-credentials: false alongside fetch-depth, preventing the GitHub token from being stored in the repository’s Git configuration.Source: Linters/SAST tools
libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/IosProductPayloadNormalizer.kt (1)
10-10: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMove the iOS marker to the function suffix.
Rename this to
normalizeProductPayloadIOSand update its callers.As per coding guidelines, platform identifiers must always appear as a suffix, never as a prefix.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/IosProductPayloadNormalizer.kt` at line 10, Rename normalizeIosProductPayload to normalizeProductPayloadIOS and update every caller and reference to use the new suffix-based name, preserving the existing behavior.Source: Coding guidelines
libraries/kmp-iap/library/src/iosTest/kotlin/io/github/hyochan/kmpiap/IosProductPayloadNormalizerTest.kt (1)
8-8: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMove the iOS marker to the class suffix.
Rename the class and matching file to
ProductPayloadNormalizerTestIOS.As per coding guidelines, platform identifiers must always appear as a suffix, never as a prefix.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/kmp-iap/library/src/iosTest/kotlin/io/github/hyochan/kmpiap/IosProductPayloadNormalizerTest.kt` at line 8, Rename the test class IosProductPayloadNormalizerTest to ProductPayloadNormalizerTestIOS and rename the matching file accordingly, ensuring all references use the new class name and the platform identifier appears only as the suffix.Source: Coding guidelines
libraries/kmp-iap/library/src/androidUnitTest/kotlin/io/github/hyochan/kmpiap/ProductStatusAndroidTest.kt (1)
9-32: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftCover the status-aware query branches.
These tests only exercise helper functions. Add handler-level coverage for cache hits/misses, non-OK Billing responses, and
Allquery status arbitration.As per coding guidelines, new code paths should be tested with the aim of full branch coverage.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@libraries/kmp-iap/library/src/androidUnitTest/kotlin/io/github/hyochan/kmpiap/ProductStatusAndroidTest.kt` around lines 9 - 32, The ProductStatusAndroidTest class only tests mapping and placeholder helpers, leaving query-handler branches uncovered. Add handler-level tests covering cached and uncached products, non-OK Billing responses, and arbitration behavior for the All query status, using the relevant query handler and Billing response symbols from the implementation. Verify returned products, statuses, and cache interactions for each branch.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt`:
- Around line 848-855: Update the non-OK BillingResult branch in the product
query flow to resume with a PurchaseException containing the mapped Billing
response code, rather than returning an empty ProductQueryOutcome with
succeeded=false. Use the existing billing-result mapping and exception patterns
in InAppPurchaseAndroid to preserve the failure details.
In
`@libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/IosProductPayloadNormalizer.kt`:
- Around line 13-18: Update applyAlias in IosProductPayloadNormalizer so empty
canonical maps/dictionaries are considered missing, not only empty lists or null
values. Extend the isMissing check to detect an empty Map and allow a non-null
normalized[legacy] value to populate the canonical key.
In `@packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt`:
- Around line 302-322: The ProductQueryType.All queries currently swallow
cancellation and other fatal errors through runCatching. In both in-app and
subscription query blocks surrounding queryProductDetailsWithStatus, rethrow
CancellationException and Error before assigning firstQueryError, while
recording only recoverable failures.
In `@packages/gql/codegen/plugins/gdscript.ts`:
- Around line 421-461: Update generateListFromDictAssignment to validate list
elements before appending, especially in the object/input, enum, and scalar
branches. Ensure incompatible or malformed items are skipped rather than written
to typed Array[Foo] or enum arrays, while preserving valid conversions and enum
fallback behavior.
---
Nitpick comments:
In @.github/workflows/ci-godot-iap.yml:
- Around line 31-33: Configure the checkout step using actions/checkout@v7 to
set persist-credentials: false alongside fetch-depth, preventing the GitHub
token from being stored in the repository’s Git configuration.
In
`@libraries/kmp-iap/library/src/androidUnitTest/kotlin/io/github/hyochan/kmpiap/ProductStatusAndroidTest.kt`:
- Around line 9-32: The ProductStatusAndroidTest class only tests mapping and
placeholder helpers, leaving query-handler branches uncovered. Add handler-level
tests covering cached and uncached products, non-OK Billing responses, and
arbitration behavior for the All query status, using the relevant query handler
and Billing response symbols from the implementation. Verify returned products,
statuses, and cache interactions for each branch.
In
`@libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/IosProductPayloadNormalizer.kt`:
- Line 10: Rename normalizeIosProductPayload to normalizeProductPayloadIOS and
update every caller and reference to use the new suffix-based name, preserving
the existing behavior.
In
`@libraries/kmp-iap/library/src/iosTest/kotlin/io/github/hyochan/kmpiap/IosProductPayloadNormalizerTest.kt`:
- Line 8: Rename the test class IosProductPayloadNormalizerTest to
ProductPayloadNormalizerTestIOS and rename the matching file accordingly,
ensuring all references use the new class name and the platform identifier
appears only as the suffix.
In
`@libraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt`:
- Line 966: The native-to-Nitro mapping for `purchaseOptionId` and
`debugDescription` is untested. Add a focused mapper test or exercise the
`fetchProducts` path using native product fixtures, then assert both fields are
copied correctly into the resulting `NitroProduct`, including coverage for
relevant branches.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f5bd6e01-d554-45fc-a7d3-61417e58644a
⛔ Files ignored due to path filters (1)
packages/gql/src/generated/types.gdis excluded by!**/generated/**
📒 Files selected for processing (36)
.github/workflows/ci-godot-iap.yml.github/workflows/ci-kmp-iap.yml.github/workflows/release-kmp.ymllibraries/flutter_inapp_purchase/lib/helpers.dartlibraries/flutter_inapp_purchase/test/helpers_unit_test.dartlibraries/godot-iap/Example/tests/test_godot_iap.gdlibraries/godot-iap/Example/tests/test_types_only.gdlibraries/godot-iap/addons/godot-iap/godot_iap.gdlibraries/godot-iap/addons/godot-iap/types.gdlibraries/kmp-iap/gradle/wrapper/gradle-wrapper.propertieslibraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/Helper.ktlibraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.ktlibraries/kmp-iap/library/src/androidUnitTest/kotlin/io/github/hyochan/kmpiap/ProductStatusAndroidTest.ktlibraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.ktlibraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/IosProductPayloadNormalizer.ktlibraries/kmp-iap/library/src/iosTest/kotlin/io/github/hyochan/kmpiap/IosProductPayloadNormalizerTest.ktlibraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.ktlibraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/ProductSerialization.ktlibraries/react-native-iap/android/src/test/java/com/margelo/nitro/iap/ProductSerializationTest.ktlibraries/react-native-iap/src/__tests__/utils/type-bridge.test.tslibraries/react-native-iap/src/specs/RnIap.nitro.tspackages/apple/Sources/Helpers/StoreKitTypesBridge.swiftpackages/apple/Sources/Models/OpenIapSerialization.swiftpackages/apple/Sources/OpenIapModule+ObjC.swiftpackages/apple/Sources/OpenIapModule.swiftpackages/apple/Tests/OpenIapTests.swiftpackages/google/openiap/src/amazon/java/dev/hyo/openiap/OpenIapModule.ktpackages/google/openiap/src/horizon/java/dev/hyo/openiap/utils/BillingConverters.ktpackages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.ktpackages/google/openiap/src/play/java/dev/hyo/openiap/helpers/Helpers.ktpackages/google/openiap/src/play/java/dev/hyo/openiap/helpers/ProductManager.ktpackages/google/openiap/src/play/java/dev/hyo/openiap/utils/BillingConverters.ktpackages/google/openiap/src/testHorizon/java/dev/hyo/openiap/HorizonStandardizedOfferTest.ktpackages/google/openiap/src/testPlay/java/dev/hyo/openiap/ProductStatusAndroidTest.ktpackages/gql/codegen/plugins/gdscript.tspackages/gql/src/generated-gdscript.test.ts
✅ Files skipped from review due to trivial changes (4)
- libraries/kmp-iap/gradle/wrapper/gradle-wrapper.properties
- libraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/ProductSerialization.kt
- packages/google/openiap/src/amazon/java/dev/hyo/openiap/OpenIapModule.kt
- libraries/godot-iap/addons/godot-iap/types.gd
🚧 Files skipped from review as they are similar to previous changes (1)
- libraries/flutter_inapp_purchase/lib/helpers.dart
There was a problem hiding this comment.
Code Review
This pull request introduces comprehensive updates across multiple platform libraries (Flutter, Godot, KMP, React Native, iOS, and Android) to support advanced subscription metadata, including pricing terms, subscription info, standardized subscription offers, discount offers, and installment plan details. It also improves robustness by handling unfetched products and status mapping under Google Play Billing Library 8, and updates the GDScript codegen to generate strongly-typed arrays. The review feedback highlights two important improvements: first, setting isAccessible = true on reflected Method objects in Helper.kt to prevent potential IllegalAccessException on non-public Billing Library classes; second, enhancing the GDScript codegen plugin to safely handle enum parsing fallbacks when string values are not found in the reverse lookup dictionary, preventing runtime type mismatch crashes in GDScript 4.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
.github/workflows/release-kmp.yml (1)
126-126: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueConsider disabling cache for the publish job's Gradle setup.
The
setup-gradleaction enables caching by default. In thepublishjob, the build artifacts are published to Maven Central and GitHub Releases — a poisoned cache could theoretically corrupt future release builds. Since this job runs in a controlled release context (not on untrusted PRs), the risk is low, but settingcache-disabled: trueeliminates it entirely if the build doesn't benefit from cross-run caching.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/release-kmp.yml at line 126, Disable Gradle caching for the release workflow’s publish job by adding the appropriate cache-disabled configuration to the gradle/actions/setup-gradle@v6 step. Update only the setup step in the publish job and preserve its existing Gradle setup behavior.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/ci-godot-iap.yml:
- Around line 10-14: Add packages/gql/src/generated/types.gd to the push
trigger’s paths list in the ci-godot-iap workflow, matching the existing
pull_request path filters so changes to generated Godot types run the Godot CI
tests.
- Around line 31-33: Add persist-credentials: false to the actions/checkout step
in the verify job, alongside fetch-depth, so the GitHub token is not stored in
the local Git configuration.
---
Nitpick comments:
In @.github/workflows/release-kmp.yml:
- Line 126: Disable Gradle caching for the release workflow’s publish job by
adding the appropriate cache-disabled configuration to the
gradle/actions/setup-gradle@v6 step. Update only the setup step in the publish
job and preserve its existing Gradle setup behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e9370353-1579-4c26-9b04-b4cd5b577d5b
⛔ Files ignored due to path filters (1)
packages/gql/src/generated/types.gdis excluded by!**/generated/**
📒 Files selected for processing (36)
.github/workflows/ci-godot-iap.yml.github/workflows/ci-kmp-iap.yml.github/workflows/release-kmp.ymllibraries/flutter_inapp_purchase/lib/helpers.dartlibraries/flutter_inapp_purchase/test/helpers_unit_test.dartlibraries/godot-iap/Example/tests/test_godot_iap.gdlibraries/godot-iap/Example/tests/test_types_only.gdlibraries/godot-iap/addons/godot-iap/godot_iap.gdlibraries/godot-iap/addons/godot-iap/types.gdlibraries/kmp-iap/gradle/wrapper/gradle-wrapper.propertieslibraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/Helper.ktlibraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.ktlibraries/kmp-iap/library/src/androidUnitTest/kotlin/io/github/hyochan/kmpiap/ProductStatusAndroidTest.ktlibraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.ktlibraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/IosProductPayloadNormalizer.ktlibraries/kmp-iap/library/src/iosTest/kotlin/io/github/hyochan/kmpiap/IosProductPayloadNormalizerTest.ktlibraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.ktlibraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/ProductSerialization.ktlibraries/react-native-iap/android/src/test/java/com/margelo/nitro/iap/ProductSerializationTest.ktlibraries/react-native-iap/src/__tests__/utils/type-bridge.test.tslibraries/react-native-iap/src/specs/RnIap.nitro.tspackages/apple/Sources/Helpers/StoreKitTypesBridge.swiftpackages/apple/Sources/Models/OpenIapSerialization.swiftpackages/apple/Sources/OpenIapModule+ObjC.swiftpackages/apple/Sources/OpenIapModule.swiftpackages/apple/Tests/OpenIapTests.swiftpackages/google/openiap/src/amazon/java/dev/hyo/openiap/OpenIapModule.ktpackages/google/openiap/src/horizon/java/dev/hyo/openiap/utils/BillingConverters.ktpackages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.ktpackages/google/openiap/src/play/java/dev/hyo/openiap/helpers/Helpers.ktpackages/google/openiap/src/play/java/dev/hyo/openiap/helpers/ProductManager.ktpackages/google/openiap/src/play/java/dev/hyo/openiap/utils/BillingConverters.ktpackages/google/openiap/src/testHorizon/java/dev/hyo/openiap/HorizonStandardizedOfferTest.ktpackages/google/openiap/src/testPlay/java/dev/hyo/openiap/ProductStatusAndroidTest.ktpackages/gql/codegen/plugins/gdscript.tspackages/gql/src/generated-gdscript.test.ts
✅ Files skipped from review due to trivial changes (2)
- packages/gql/src/generated-gdscript.test.ts
- libraries/kmp-iap/gradle/wrapper/gradle-wrapper.properties
🚧 Files skipped from review as they are similar to previous changes (31)
- packages/google/openiap/src/testPlay/java/dev/hyo/openiap/ProductStatusAndroidTest.kt
- libraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/ProductSerialization.kt
- packages/google/openiap/src/testHorizon/java/dev/hyo/openiap/HorizonStandardizedOfferTest.kt
- libraries/react-native-iap/android/src/test/java/com/margelo/nitro/iap/ProductSerializationTest.kt
- packages/google/openiap/src/play/java/dev/hyo/openiap/helpers/Helpers.kt
- .github/workflows/ci-kmp-iap.yml
- packages/google/openiap/src/amazon/java/dev/hyo/openiap/OpenIapModule.kt
- libraries/react-native-iap/src/specs/RnIap.nitro.ts
- packages/apple/Sources/Models/OpenIapSerialization.swift
- packages/apple/Sources/Helpers/StoreKitTypesBridge.swift
- packages/apple/Tests/OpenIapTests.swift
- packages/apple/Sources/OpenIapModule+ObjC.swift
- packages/gql/codegen/plugins/gdscript.ts
- libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/IosProductPayloadNormalizer.kt
- libraries/godot-iap/Example/tests/test_types_only.gd
- libraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
- packages/apple/Sources/OpenIapModule.swift
- libraries/godot-iap/Example/tests/test_godot_iap.gd
- libraries/kmp-iap/library/src/iosTest/kotlin/io/github/hyochan/kmpiap/IosProductPayloadNormalizerTest.kt
- packages/google/openiap/src/horizon/java/dev/hyo/openiap/utils/BillingConverters.kt
- packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
- libraries/godot-iap/addons/godot-iap/godot_iap.gd
- packages/google/openiap/src/play/java/dev/hyo/openiap/utils/BillingConverters.kt
- libraries/react-native-iap/src/tests/utils/type-bridge.test.ts
- libraries/flutter_inapp_purchase/test/helpers_unit_test.dart
- packages/google/openiap/src/play/java/dev/hyo/openiap/helpers/ProductManager.kt
- libraries/flutter_inapp_purchase/lib/helpers.dart
- libraries/godot-iap/addons/godot-iap/types.gd
- libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt
- libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/Helper.kt
- libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt
There was a problem hiding this comment.
Code Review
This pull request introduces comprehensive updates across multiple packages (Flutter, Godot, KMP, React Native, iOS, and Android) to enhance subscription metadata parsing, serialization, and type safety. Key additions include support for pricing terms, discount offers, installment plan details, and product status placeholders. Additionally, the GDScript code generator was updated to build typed arrays robustly from JSON. The reviewer's feedback highlights several performance improvement opportunities, specifically recommending the parallelization of independent billing queries in Kotlin using coroutineScope and async, as well as parallelizing StoreKit 2 lookups in Swift using withTaskGroup to reduce latency.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Code Review
This pull request standardizes the parsing and mapping of subscription metadata, discount offers, and product statuses across Flutter, Godot, KMP, React Native, iOS, and Android platforms. It improves product query handling to gracefully manage unfetched products by generating typed placeholders with correct status codes. Additionally, the GDScript codegen plugin has been updated to generate type-safe list parsing from dictionaries, and the GDScript types have been regenerated accordingly. Comprehensive unit tests have been added to verify these changes. I have no feedback to provide as there are no review comments.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Summary
Product.pricingTermsIOSis always empty/undefined on iOS 26.4+ (billing plan / 12-month commitment data never reaches JS) #217 React Native iOS bridge sopricingTermsIOS,subscriptionInfoIOS,debugDescription, and standardized offers survive end to end.type: allresults.productStatusAndroidand keeps Play caches isolated by product type.Fixes #217
Verification
bun audit:parityswift test(103 passed)net10.0buildgit diff --checkPreview
Preview recording is not applicable because this changes library bridge/parsing and CI behavior, not a visible UI. The terminal/API checks above are the relevant proof.
Summary by CodeRabbit
debugDescriptionand one-time offerpurchaseOptionIdin React Native/Android outputs.