Skip to content

fix(libraries): preserve product metadata bridges - #219

Merged
hyochan merged 15 commits into
mainfrom
fix/react-native-pricing-terms-ios-217
Jul 10, 2026
Merged

hyochan merged 15 commits into
mainfrom
fix/react-native-pricing-terms-ios-217

Conversation

@hyochan

@hyochan hyochan commented Jul 9, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Fixes the issue # Product.pricingTermsIOS is always empty/undefined on iOS 26.4+ (billing plan / 12-month commitment data never reaches JS) #217 React Native iOS bridge so pricingTermsIOS, subscriptionInfoIOS, debugDescription, and standardized offers survive end to end.
  • Audits and fixes equivalent product-metadata drops across Apple, Google Play, Horizon, Amazon, Flutter, Godot, and KMP bridge/parsing paths.
  • Preserves concrete product/subscription variants and requested order, including mixed type: all results.
  • Exposes Billing 8 per-product outcomes through productStatusAndroid and keeps Play caches isolated by product type.
  • Hardens KMP/Godot generated-vs-manual decoding and adds regression coverage for nested offer, discount, pricing-term, installment, and purchase-option fields.
  • Fixes the KMP CI infrastructure failure by updating Gradle setup, extending wrapper download timeout, and using bounded non-parallel memory settings.

Fixes #217

Verification

  • bun audit:parity
  • GQL codegen tests: 22 passed; generated GDScript sync is idempotent
  • Apple: swift test (103 passed)
  • Google: Play, Horizon, and Amazon compile plus flavor unit tests
  • React Native: typecheck, lint, full Jest, Android compile, and Android unit tests
  • Flutter: analyze, full test suite (277 passed), and example Android APK build
  • Godot: generated-type tests (99 passed), wrapper tests (29 passed), and iOS GDExtension Swift build
  • KMP: Play/Horizon/Amazon library compiles, all three example APKs, Play unit tests, and iOS simulator tests
  • Expo: TypeScript check, package tests (291 passed), plugin tests (75 passed), and module build
  • MAUI: shared net10.0 build
  • Workflow YAML parse and git diff --check

Preview

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

  • New Features
    • Expanded iOS/Android metadata coverage for in-app purchases and subscriptions (pricing terms, subscription info/offers, discounts, installment details, product status), including non-renewing subscription support.
    • Added/propagated debugDescription and one-time offer purchaseOptionId in React Native/Android outputs.
    • Improved generated GDScript list deserialization.
  • Bug Fixes
    • Standardized subscription/offer decoding/serialization across legacy and new payload shapes, including billing-period derivation and consistent handling of empty/missing offers.
  • Tests
    • Updated and added unit tests for iOS/Android parsing, installment/pricing-phase preservation, invalid iOS metadata handling, and conversion fidelity.
  • Chores
    • CI/test workflow updates and additional headless Godot test runs.

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.
@coderabbitai

coderabbitai Bot commented Jul 9, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@hyochan, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 8 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a5334fa9-5c7f-4aa1-b9d2-917d04c35cfa

📥 Commits

Reviewing files that changed from the base of the PR and between 98c22eb and 213ed8b.

⛔ Files ignored due to path filters (1)
  • packages/gql/src/generated/types.gd is excluded by !**/generated/**
📒 Files selected for processing (17)
  • .github/workflows/ci-godot-iap.yml
  • libraries/godot-iap/Example/tests/test_types_only.gd
  • libraries/godot-iap/addons/godot-iap/types.gd
  • libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/Helper.kt
  • libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt
  • libraries/kmp-iap/library/src/androidUnitTest/kotlin/io/github/hyochan/kmpiap/ProductStatusAndroidTest.kt
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/ProductPayloadNormalizerIOS.kt
  • libraries/kmp-iap/library/src/iosTest/kotlin/io/github/hyochan/kmpiap/ProductPayloadNormalizerTestIOS.kt
  • libraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
  • libraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/ProductSerialization.kt
  • libraries/react-native-iap/android/src/test/java/com/margelo/nitro/iap/ProductSerializationTest.kt
  • packages/apple/Sources/OpenIapModule.swift
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/testPlay/java/dev/hyo/openiap/ProductStatusAndroidTest.kt
  • packages/gql/codegen/plugins/gdscript.ts
  • packages/gql/src/generated-gdscript.test.ts

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Propagates standardized product, subscription, offer, status, pricing, serialization, query, and generated-model metadata across Flutter, React Native, KMP, Apple, Google, Horizon, Godot, and GraphQL integrations.

Changes

Flutter product and offer decoding

Layer / File(s) Summary
Standardized product and offer parsing
libraries/flutter_inapp_purchase/lib/helpers.dart
Adds typed list parsing, standardized iOS pricing and offer fields, Android discount/status/installment mapping, billing-period parsing, and purchase option identifiers.
Flutter parsing coverage
libraries/flutter_inapp_purchase/test/helpers_unit_test.dart
Adds coverage for iOS pricing terms and legacy offers, Android discounts, statuses, installment details, purchase option identifiers, and error metadata.

React Native bridge metadata

Layer / File(s) Summary
Nitro contract and native serialization
libraries/react-native-iap/src/specs/..., libraries/react-native-iap/ios/..., libraries/react-native-iap/android/...
Extends Nitro product and offer fields and serializes iOS and Android metadata through native bridge conversions.
Type bridge parsing and tests
libraries/react-native-iap/src/utils/..., libraries/react-native-iap/src/__tests__/..., libraries/react-native-iap/android/src/test/...
Parses validated iOS JSON metadata and verifies Android offer, installment, discount, and purchase-option fields.

KMP and platform product flows

Layer / File(s) Summary
Android mapping and status-aware querying
libraries/kmp-iap/library/src/androidMain/..., packages/google/openiap/src/play/...
Maps richer Android offers, exposes unfetched product statuses, uses product-type-aware caching, and creates unavailable product results.
iOS normalization and DSL conversion
libraries/kmp-iap/library/src/iosMain/..., libraries/kmp-iap/library/src/commonMain/..., libraries/kmp-iap/library/src/commonTest/...
Normalizes legacy iOS aliases, decodes generated models with fallbacks, reconciles offers, and preserves metadata through DSL conversion.

Apple, Horizon, Amazon, Godot, and generated model updates

Layer / File(s) Summary
Platform product serialization and variants
packages/apple/..., packages/google/openiap/src/{horizon,amazon}/..., libraries/godot-iap/...
Preserves Apple product variants and ordering, maps Horizon offers and statuses, forwards Amazon descriptions, and distinguishes Godot subscription product types.
Typed generated decoding and validation
packages/gql/..., libraries/godot-iap/Example/tests/..., libraries/godot-iap/addons/godot-iap/types.gd
Generates typed list deserialization for objects, enums, and scalars and tests nested product arrays and variant mapping.

CI workflow updates

Layer / File(s) Summary
CI execution configuration
.github/workflows/*, libraries/kmp-iap/gradle/wrapper/gradle-wrapper.properties
Updates Gradle actions and memory settings, adds Godot setup and headless tests, and increases the Gradle wrapper network timeout.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested labels: ፦ refactor

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR adds pricingTermsIOS to the Nitro spec, serializes it natively, and parses it in TypeScript, addressing #217.
Out of Scope Changes check ✅ Passed The extra changes across other libraries, tests, and CI support the same metadata-propagation work and don't look unrelated.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: preserving product metadata across library bridges.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/react-native-pricing-terms-ios-217

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@hyochan hyochan added 🐛 bug Something isn't working 🛠 bugfix All kinds of bug fixes 📱 iOS Related to iOS 🤖 android Related to android cross-platform Cross-platform (both Android & iOS) react-native-iap react-native-iap library flutter_inapp_purchase flutter_inapp_purchase library godot-iap godot-iap library kmp-iap kmp-iap library labels Jul 9, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread libraries/flutter_inapp_purchase/lib/helpers.dart

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread libraries/flutter_inapp_purchase/lib/helpers.dart Outdated
Keep parsing valid generated product metadata list entries when one malformed entry is present, and cover the mixed-list case in helper tests.
@hyochan

hyochan commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

Addressed Gemini parser robustness feedback in 1561c22.

Changes:

  • _parseGeneratedList now skips malformed entries instead of discarding the entire list.
  • Added Flutter helper coverage for a mixed malformed/valid metadata list.
  • Re-ran flutter test test/helpers_unit_test.dart and dart analyze lib/helpers.dart test/helpers_unit_test.dart.

Avoid generated int casts for native installment plan details and cover string/double platform-channel numeric values in helper tests.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

medium

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)

medium

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)

medium

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)

medium

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")
            }
        }

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread libraries/flutter_inapp_purchase/lib/helpers.dart Outdated
Handle already-typed Flutter metadata values, preserve defensive installment parsing, and avoid serializing empty iOS metadata collections over the React Native bridge.
@hyochan

hyochan commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

Addressed the latest Gemini top-level review feedback in 48a3eca.

Changes:

  • _parseGeneratedList now accepts already-typed list/items and skips malformed entries.
  • _parseProductStatusAndroid now returns already-typed ProductStatusAndroid values directly.
  • Installment plan metadata keeps defensive numeric parsing for string/double/native numeric values.
  • React Native iOS helper now avoids serializing empty pricingTermsIOS arrays or subscriptionInfoIOS dictionaries.

Verification:

  • flutter test test/helpers_unit_test.dart
  • dart analyze lib/helpers.dart test/helpers_unit_test.dart
  • git diff --check

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Preserve legacy offers during iOS model decoding. ProductIOS.fromJson and ProductSubscriptionIOS.fromJson only read subscriptionOffers, and OpenIapSerialization.encode always tags payloads with __typename, so the generated decode path will usually succeed and skip the manual map["subscriptionOffers"] ?: map["offers"] recovery. That drops offer metadata for older payloads in convertAnyListToProducts, convertAnyListToProductSubscriptions, and convertAnyListToProductOrSubscriptions; 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 win

Optional: add a negative-path test for pricingTermsIOS/subscriptionInfoIOS.

The existing suite has an "invalid JSON" test for subscriptionOffers (line 287) but none for pricingTermsIOS/subscriptionInfoIOS. Adding one would cover the catch fallback-to-null branches 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 win

Consider 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 like parseJsonField<T>(value: string | null | undefined): T | null would 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

_parseGeneratedList discards the whole list on one bad item.

A null map is skipped (continue), but a fromJson throw returns null for the entire list. For fields without a fallback (pricingTermsIOS, discountOffers), one malformed entry silently drops all valid entries. If per-item resilience is desired, catch and continue instead 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

📥 Commits

Reviewing files that changed from the base of the PR and between 71e1755 and e120dcd.

📒 Files selected for processing (9)
  • libraries/flutter_inapp_purchase/lib/helpers.dart
  • libraries/flutter_inapp_purchase/test/helpers_unit_test.dart
  • libraries/godot-iap/ios-gdextension/Sources/GodotIap/GodotIap.swift
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt
  • libraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
  • libraries/react-native-iap/ios/RnIapHelper.swift
  • libraries/react-native-iap/src/__tests__/utils/type-bridge.test.ts
  • libraries/react-native-iap/src/specs/RnIap.nitro.ts
  • libraries/react-native-iap/src/utils/type-bridge.ts

Comment thread libraries/flutter_inapp_purchase/lib/helpers.dart Outdated
Pass decoded subscription offer lists to both generated and legacy iOS parsers, and cover legacy JSON string offers that require fallback parsing.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread libraries/react-native-iap/ios/RnIapHelper.swift
Comment thread libraries/react-native-iap/src/utils/type-bridge.ts
Fall back to legacy subscription metadata keys, validate parsed iOS metadata JSON shapes, and cover invalid-shape handling in type-bridge tests.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
@hyochan

hyochan commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

Addressed CodeRabbit KMP legacy offers feedback in 0bc8fc8.

Changes:

  • Successful Product/ProductSubscription/ProductOrSubscription decode paths now merge legacy offers payloads when subscriptionOffers is absent.
  • Preserves the existing generated decoder path while keeping older cached native payload offer metadata.
  • Also extended React Native metadata invalid JSON coverage.

Verification:

  • cd libraries/kmp-iap && ./gradlew :library:compileKotlinIosSimulatorArm64
  • cd libraries/react-native-iap && yarn jest src/tests/utils/type-bridge.test.ts --runInBand --coverage=false
  • cd libraries/react-native-iap && yarn typecheck:lib
  • cd libraries/react-native-iap && yarn eslint src/tests/utils/type-bridge.test.ts
  • git diff --check

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread libraries/react-native-iap/ios/RnIapHelper.swift

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

convertAnyToProductIOS skips the legacy-offer merge that its sibling conversion functions apply.

convertAnyListToProducts, convertAnyListToProductSubscriptions, and convertAnyListToProductOrSubscriptions all call mergeLegacySubscriptionOffers(it, map) after a successful fromJson decode so that legacy subscriptionOffers/offers payloads are backfilled when the decoded model's own field is empty. convertAnyToProductIOS (used by getPromotedProductIOS()) returns the fromJson result directly at Line 1456 without this merge step, so a promoted product with legacy-format offer data would silently lose subscriptionOffers where 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 win

Legacy-offer extraction is duplicated across the three mergeLegacySubscriptionOffers overloads.

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 mergeLegacySubscriptionOffers overload can short-circuit on legacySubscriptionOffersOrNull(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 value

Redundant branch: both paymentMode == PaymentMode.FreeTrial and else resolve to Introductory.

The when-block's second and third branches both return DiscountOfferType.Introductory, making the explicit FreeTrial check 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 win

Duplicated extraction logic between toOfferDetail() and toDiscountOffer().

Both extension functions independently rebuild DiscountDisplayInfoAndroid, LimitedQuantityInfoAndroid, PreorderDetailsAndroid, RentalDetailsAndroid, and ValidTimeWindowAndroid from the same OneTimePurchaseOfferDetails receiver, 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

📥 Commits

Reviewing files that changed from the base of the PR and between f5ab981 and c6fd941.

📒 Files selected for processing (8)
  • libraries/flutter_inapp_purchase/lib/helpers.dart
  • libraries/flutter_inapp_purchase/test/helpers_unit_test.dart
  • libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/Helper.kt
  • libraries/kmp-iap/library/src/commonMain/kotlin/io/github/hyochan/kmpiap/DslExtensions.kt
  • libraries/kmp-iap/library/src/commonTest/kotlin/io/github/hyochan/kmpiap/InAppPurchaseTest.kt
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt
  • libraries/react-native-iap/ios/RnIapHelper.swift
  • libraries/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

@hyochan

hyochan commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

Addressed CodeRabbit outside-diff KMP promoted product feedback in a3e341b.

Changes:

  • convertAnyToProductIOS now applies the same legacy subscriptionOffers/offers merge used by the list conversion paths after a successful ProductIOS.fromJson decode.

Verification:

  • cd libraries/kmp-iap && ./gradlew :library:compileKotlinIosSimulatorArm64 --console=plain

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Use concrete iOS decoders before the manual fallback.

Product.fromJson / ProductSubscription.fromJson require __typename; raw iOS maps without it fall through to the manual constructors, where subscriptionInfoIOS is still set to null. 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 win

Fall back to legacy offers when subscriptionOffers is empty.

?: only handles null/missing values. If subscriptionOffers is [] while legacy offers has 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 product
     private fun mergeLegacySubscriptionOffers(
         subscription: ProductSubscription,
         map: Map<String, Any?>
     ): ProductSubscription {
-        val subscriptionOffers = convertAnyListToSubscriptionOffers(
-            map["subscriptionOffers"] ?: map["offers"]
-        )
+        val subscriptionOffers = subscriptionOffersFrom(map)
         if (subscriptionOffers.isEmpty()) return subscription

Also 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

📥 Commits

Reviewing files that changed from the base of the PR and between c6fd941 and a3e341b.

📒 Files selected for processing (1)
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread libraries/flutter_inapp_purchase/lib/helpers.dart Outdated
@hyochan

hyochan commented Jul 9, 2026

Copy link
Copy Markdown
Member Author

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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Jul 9, 2026
@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Jul 9, 2026
@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Jul 9, 2026
@hyochan hyochan added the 💨 ci Cloud integration label Jul 10, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 lift

Cover the native-to-Nitro assignments.

The supplied tests construct NitroProduct fixtures directly, so they cannot detect either assignment being removed or mapped incorrectly. Add a fetchProducts-path test (or a focused mapper test) asserting purchaseOptionId and debugDescription reach 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 win

Set persist-credentials: false on the checkout action.

The checkout action persists a GitHub token in .git/config by 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 win

Move the iOS marker to the function suffix.

Rename this to normalizeProductPayloadIOS and 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 win

Move 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 lift

Cover the status-aware query branches.

These tests only exercise helper functions. Add handler-level coverage for cache hits/misses, non-OK Billing responses, and All query 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

📥 Commits

Reviewing files that changed from the base of the PR and between a3e341b and 019f2f4.

⛔ Files ignored due to path filters (1)
  • packages/gql/src/generated/types.gd is excluded by !**/generated/**
📒 Files selected for processing (36)
  • .github/workflows/ci-godot-iap.yml
  • .github/workflows/ci-kmp-iap.yml
  • .github/workflows/release-kmp.yml
  • libraries/flutter_inapp_purchase/lib/helpers.dart
  • libraries/flutter_inapp_purchase/test/helpers_unit_test.dart
  • libraries/godot-iap/Example/tests/test_godot_iap.gd
  • libraries/godot-iap/Example/tests/test_types_only.gd
  • libraries/godot-iap/addons/godot-iap/godot_iap.gd
  • libraries/godot-iap/addons/godot-iap/types.gd
  • libraries/kmp-iap/gradle/wrapper/gradle-wrapper.properties
  • libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/Helper.kt
  • libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt
  • libraries/kmp-iap/library/src/androidUnitTest/kotlin/io/github/hyochan/kmpiap/ProductStatusAndroidTest.kt
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/IosProductPayloadNormalizer.kt
  • libraries/kmp-iap/library/src/iosTest/kotlin/io/github/hyochan/kmpiap/IosProductPayloadNormalizerTest.kt
  • libraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
  • libraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/ProductSerialization.kt
  • libraries/react-native-iap/android/src/test/java/com/margelo/nitro/iap/ProductSerializationTest.kt
  • libraries/react-native-iap/src/__tests__/utils/type-bridge.test.ts
  • libraries/react-native-iap/src/specs/RnIap.nitro.ts
  • packages/apple/Sources/Helpers/StoreKitTypesBridge.swift
  • packages/apple/Sources/Models/OpenIapSerialization.swift
  • packages/apple/Sources/OpenIapModule+ObjC.swift
  • packages/apple/Sources/OpenIapModule.swift
  • packages/apple/Tests/OpenIapTests.swift
  • packages/google/openiap/src/amazon/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/utils/BillingConverters.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/helpers/Helpers.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/helpers/ProductManager.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/utils/BillingConverters.kt
  • packages/google/openiap/src/testHorizon/java/dev/hyo/openiap/HorizonStandardizedOfferTest.kt
  • packages/google/openiap/src/testPlay/java/dev/hyo/openiap/ProductStatusAndroidTest.kt
  • packages/gql/codegen/plugins/gdscript.ts
  • packages/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

Comment thread packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt Outdated
Comment thread packages/gql/codegen/plugins/gdscript.ts

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/gql/codegen/plugins/gdscript.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
.github/workflows/release-kmp.yml (1)

126-126: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider disabling cache for the publish job's Gradle setup.

The setup-gradle action enables caching by default. In the publish job, 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 setting cache-disabled: true eliminates 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

📥 Commits

Reviewing files that changed from the base of the PR and between a3e341b and 98c22eb.

⛔ Files ignored due to path filters (1)
  • packages/gql/src/generated/types.gd is excluded by !**/generated/**
📒 Files selected for processing (36)
  • .github/workflows/ci-godot-iap.yml
  • .github/workflows/ci-kmp-iap.yml
  • .github/workflows/release-kmp.yml
  • libraries/flutter_inapp_purchase/lib/helpers.dart
  • libraries/flutter_inapp_purchase/test/helpers_unit_test.dart
  • libraries/godot-iap/Example/tests/test_godot_iap.gd
  • libraries/godot-iap/Example/tests/test_types_only.gd
  • libraries/godot-iap/addons/godot-iap/godot_iap.gd
  • libraries/godot-iap/addons/godot-iap/types.gd
  • libraries/kmp-iap/gradle/wrapper/gradle-wrapper.properties
  • libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/Helper.kt
  • libraries/kmp-iap/library/src/androidMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseAndroid.kt
  • libraries/kmp-iap/library/src/androidUnitTest/kotlin/io/github/hyochan/kmpiap/ProductStatusAndroidTest.kt
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/InAppPurchaseIOS.kt
  • libraries/kmp-iap/library/src/iosMain/kotlin/io/github/hyochan/kmpiap/IosProductPayloadNormalizer.kt
  • libraries/kmp-iap/library/src/iosTest/kotlin/io/github/hyochan/kmpiap/IosProductPayloadNormalizerTest.kt
  • libraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
  • libraries/react-native-iap/android/src/main/java/com/margelo/nitro/iap/ProductSerialization.kt
  • libraries/react-native-iap/android/src/test/java/com/margelo/nitro/iap/ProductSerializationTest.kt
  • libraries/react-native-iap/src/__tests__/utils/type-bridge.test.ts
  • libraries/react-native-iap/src/specs/RnIap.nitro.ts
  • packages/apple/Sources/Helpers/StoreKitTypesBridge.swift
  • packages/apple/Sources/Models/OpenIapSerialization.swift
  • packages/apple/Sources/OpenIapModule+ObjC.swift
  • packages/apple/Sources/OpenIapModule.swift
  • packages/apple/Tests/OpenIapTests.swift
  • packages/google/openiap/src/amazon/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/horizon/java/dev/hyo/openiap/utils/BillingConverters.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/OpenIapModule.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/helpers/Helpers.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/helpers/ProductManager.kt
  • packages/google/openiap/src/play/java/dev/hyo/openiap/utils/BillingConverters.kt
  • packages/google/openiap/src/testHorizon/java/dev/hyo/openiap/HorizonStandardizedOfferTest.kt
  • packages/google/openiap/src/testPlay/java/dev/hyo/openiap/ProductStatusAndroidTest.kt
  • packages/gql/codegen/plugins/gdscript.ts
  • packages/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

Comment thread .github/workflows/ci-godot-iap.yml
Comment thread .github/workflows/ci-godot-iap.yml

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/apple/Sources/OpenIapModule.swift

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Jul 10, 2026
@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Jul 10, 2026
@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Jul 10, 2026
@hyodotdev hyodotdev deleted a comment from coderabbitai Bot Jul 10, 2026
@hyochan
hyochan merged commit b5d6916 into main Jul 10, 2026
16 checks passed
@hyochan
hyochan deleted the fix/react-native-pricing-terms-ios-217 branch July 10, 2026 08:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🤖 android Related to android 🐛 bug Something isn't working 🛠 bugfix All kinds of bug fixes 💨 ci Cloud integration cross-platform Cross-platform (both Android & iOS) flutter_inapp_purchase flutter_inapp_purchase library godot-iap godot-iap library 📱 iOS Related to iOS kmp-iap kmp-iap library react-native-iap react-native-iap library

Projects

None yet

Development

Successfully merging this pull request may close these issues.

# Product.pricingTermsIOS is always empty/undefined on iOS 26.4+ (billing plan / 12-month commitment data never reaches JS)

1 participant