Skip to content

fix(types): add missing discountsIOS and other platform-specific fields#3046

Merged
hyochan merged 7 commits intomainfrom
fix/missing-types
Oct 2, 2025
Merged

fix(types): add missing discountsIOS and other platform-specific fields#3046
hyochan merged 7 commits intomainfrom
fix/missing-types

Conversation

@hyochan
Copy link
Owner

@hyochan hyochan commented Oct 2, 2025

Summary

  • Add missing discountsIOS field to Product types for iOS subscription promotional offers
  • Add 17 missing iOS-specific Purchase fields (appBundleIdIOS, environmentIOS, etc.)
  • Add missing Android-specific fields (nameAndroid, oneTimePurchaseOfferDetailsAndroid, developerPayloadAndroid)
  • Improve type safety by converting oneTimePurchaseOfferDetailsAndroid from JSON string to typed object
  • Remove all as any type assertions throughout type-bridge for better type safety
  • Add global DataModal context to display full purchase data in example apps

Changes

Core Library

iOS (StoreKit 2)

  • Add discountsIOS field with JSON serialization for promotional offers
  • Add 17 iOS Purchase fields: appBundleIdIOS, countryCodeIOS, currencyCodeIOS, currencySymbolIOS, environmentIOS, expirationDateIOS, isUpgradedIOS, offerIOS, ownershipTypeIOS, reasonIOS, reasonStringRepresentationIOS, revocationDateIOS, revocationReasonIOS, storefrontCountryCodeIOS, subscriptionGroupIdIOS, transactionReasonIOS, webOrderLineItemIdIOS
  • Add offerIOS field with proper JSON serialization

Android (Play Billing)

  • Add nameAndroid field to Product types
  • Add oneTimePurchaseOfferDetailsAndroid with proper typed interface (NitroOneTimePurchaseOfferDetail)
  • Add developerPayloadAndroid to Purchase types
  • Convert oneTimePurchaseOfferDetailsAndroid from JSON string to typed object for better type safety

Type Safety Improvements

  • Create NitroOneTimePurchaseOfferDetail interface in Nitro spec
  • Remove all as any type assertions in type-bridge.ts
  • Add proper type handling for all new fields

Example Apps

Example (React Native)

  • Add DataModalContext for global modal management
  • Update AvailablePurchases screen to use global modal
  • Add DataModalProvider to App.tsx
  • Update tests with renderWithProviders helper
  • All tests passing (7 passing, 2 skipped)

Example-Expo

  • Sync all changes from example project
  • Add DataModalContext with identical implementation
  • Update copy-screens.sh to handle DataModalContext import path transformation
  • Fix ESLint errors in subscription-flow.tsx:
    • Convert Array to T[] syntax
    • Add displayName to PlanChangeControls component
    • Add missing dependencies to useCallback

Test Plan

  • iOS build successful after changes
  • Example app tests passing (7/7)
  • Example-expo ESLint checks passing
  • TypeScript compilation clean
  • Nitro bridge files regenerated successfully

Summary by CodeRabbit

  • New Features

    • Global data modal to view/copy JSON (sensitive fields redacted); provider wired into app and examples.
    • Android: show one-time purchase offer details and product name; preserve developer payload for purchases.
    • Purchase flow: storefront display with manual refresh; subscription flow: plan-change controls and cached purchases.
  • Bug Fixes

    • Expanded iOS/Android purchase & product metadata, improved parsing and null handling; better purchase error messaging.
  • Refactor

    • Replaced per-screen modals with a shared provider across example apps.
  • Tests

    • Tests updated to render components with the new provider.
  • Chores

    • Script and CI tweaks for example project.

@coderabbitai
Copy link

coderabbitai bot commented Oct 2, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds Android one-time-offer details and product name to Nitro payloads, expands Nitro types and purchase mappings with many iOS-prefixed fields plus developerPayloadAndroid, and introduces a shared DataModal context used by example apps while refactoring example purchase/subscription flows to use storefronts, caching, and centralized data display.

Changes

Cohort / File(s) Summary
Android bridge: product/purchase mapping
android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
Extracts nameAndroid; maps Android one-time offer into oneTimePurchaseOfferDetailsAndroid (NitroOneTimePurchaseOfferDetail); includes developerPayloadAndroid in purchases; emits many iOS-prefixed fields as null for Android conversions.
iOS bridge: product/purchase fields
ios/RnIapHelper.swift
Serializes/parses discountsIOS and offerIOS; populates additional iOS-specific product/purchase fields (appBundleIdIOS, countryCodeIOS, currencyCodeIOS, currencySymbolIOS, environmentIOS, expirationDateIOS, isUpgradedIOS, ownershipTypeIOS, reasonIOS, reasonStringRepresentationIOS, revocationDateIOS, revocationReasonIOS, storefrontCountryCodeIOS, subscriptionGroupIdIOS, transactionReasonIOS, webOrderLineItemIdIOS).
Nitro spec & type bridge
src/specs/RnIap.nitro.ts, src/utils/type-bridge.ts
Adds NitroOneTimePurchaseOfferDetail; extends NitroProduct/NitroPurchase with discountsIOS, nameAndroid, oneTimePurchaseOfferDetailsAndroid, developerPayloadAndroid and many iOS fields; improves JSON parsing, null handling and mapping logic between Nitro types and platform types.
Data modal context (shared)
example-expo/contexts/DataModalContext.tsx, example/src/contexts/DataModalContext.tsx
New DataModalProvider and useDataModal; centralized JSON viewer modal that redacts purchaseToken, supports copy-to-clipboard, show/hide API, and clears data after close.
App/provider wiring
example/App.tsx, example-expo/app/_layout.tsx
Wraps example app root navigators with DataModalProvider so components can call useDataModal() globally.
Available Purchases (UI)
example/screens/AvailablePurchases.tsx, example-expo/app/available-purchases.tsx
Removes local modal/clipboard logic and per-item modal state; item press now calls showData(...) from useDataModal() to display purchase JSON via global data modal.
Purchase flow: storefront refactor
example-expo/app/purchase-flow.tsx
Replaces available-purchases props with storefront props/state, adds getStorefront fetch flow, updates UI and error handling, and exposes onRefreshStorefront.
Subscription flow: plan change and caching
example-expo/app/subscription-flow.tsx, example/screens/SubscriptionFlow.tsx
Adds PlanChangeControls, cached available purchases and lastPurchasedPlan tracking; uses ProductSubscriptionAndroid/extended purchase types for Android plan-change flows (replacementMode/proration/token handling); refreshes caches after purchase.
Tests and scripts
example/__tests__/screens/AvailablePurchases.test.tsx, example-expo/scripts/copy-screens.sh
Tests updated to render components with DataModalProvider; generation script now rewrites DataModalContext import path during copy.
CI
.github/workflows/ci-example-expo.yml
Comments out lint step for example-expo workflow; retains typecheck step.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Screen as AvailablePurchases
  participant Ctx as DataModalProvider
  participant Modal as DataModal

  User->>Screen: tap purchase item
  Screen->>Ctx: showData(purchase, "Purchase: <id>")
  Ctx->>Modal: set visible, title, payload (redact purchaseToken)
  Modal-->>User: display JSON
  User->>Modal: Copy / Close
  Modal->>Ctx: hideModal()
  Ctx->>Modal: clear data after delay
Loading
sequenceDiagram
  autonumber
  actor App as PurchaseFlowContainer
  participant IAP as react-native-iap
  participant UI as PurchaseFlow

  App->>IAP: connect()
  App->>IAP: getStorefront()
  IAP-->>App: storefront | error
  App->>UI: provide {storefront, isFetchingStorefront, onRefreshStorefront}
  User->>UI: request refresh
  UI->>App: onRefreshStorefront()
  App->>IAP: getStorefront()
  IAP-->>App: storefront
  App-->>UI: update props
Loading
sequenceDiagram
  autonumber
  actor User
  participant Sub as SubscriptionFlow
  participant IAP as react-native-iap
  participant Store as PlatformStore

  User->>Sub: choose plan change
  alt Android path
    Sub->>IAP: requestPurchase(sku, {offerToken, replacementMode, purchaseToken})
  else iOS path
    Sub->>IAP: requestSubscription(sku, options)
  end
  IAP-->>Sub: purchase result
  Sub->>IAP: getAvailablePurchases() / getSubscriptions()
  IAP-->>Sub: updated caches
  Sub-->>User: show updated plan state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

🛠 refactor, 📗 example

Poem

hop-hop, I stitched fields with carroted care,
Android names and one-time offers now appear.
A modal shows JSON, tokens tucked away,
copy to clipboard, then hop off to play.
tiny paws, big changes—🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “fix(types): add missing discountsIOS and other platform-specific fields” clearly summarizes the primary change of adding missing type definitions for platform-specific fields in the core library. It follows a concise conventional-commit style, directly reflecting the main update without extraneous details. This makes it easy for reviewers and future readers to understand the core intent at a glance.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/missing-types

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 and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @hyochan, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the library's capability to handle platform-specific in-app purchase and product details by adding numerous missing fields for both iOS and Android. It also brings substantial improvements to type safety by introducing structured types and eliminating 'any' assertions. Furthermore, the example applications have been refactored to use a new global data display modal and feature more sophisticated Android subscription management, making them more robust and user-friendly.

Highlights

  • Enhanced Platform-Specific Fields: Added discountsIOS to Product types for iOS promotional offers, 17 new iOS-specific Purchase fields (e.g., appBundleIdIOS, environmentIOS, expirationDateIOS), and several Android-specific fields (nameAndroid, oneTimePurchaseOfferDetailsAndroid, developerPayloadAndroid) to improve data completeness across platforms.
  • Improved Type Safety: Converted oneTimePurchaseOfferDetailsAndroid from a raw JSON string to a structured, typed object (NitroOneTimePurchaseOfferDetail) for better data handling and removed all as any type assertions within the type-bridge utility, enhancing overall code reliability.
  • Global Data Modal Context: Introduced a new global DataModalContext in the example applications to centralize the display of full purchase data, replacing redundant local modal implementations in AvailablePurchases and SubscriptionFlow screens for a more consistent user experience.
  • Refined Android Subscription Management: Implemented robust logic for Android subscription plan changes (upgrade/downgrade) within the example app's subscription flow, including dynamic detection of the current plan and fetching necessary purchase tokens for replacement requests.
  • Example App Clean-up and Storefront Display: Removed unused availablePurchases logic and PurchaseDetails components from example apps, and added functionality to display and refresh the current storefront information, providing more relevant context to users.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@hyochan hyochan added 🛠 bugfix All kinds of bug fixes ❄️ types Typing issues 🎧 essential labels Oct 2, 2025
@codecov
Copy link

codecov bot commented Oct 2, 2025

Codecov Report

❌ Patch coverage is 81.81818% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.97%. Comparing base (474462f) to head (493f4cf).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
src/utils/type-bridge.ts 81.81% 6 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3046      +/-   ##
==========================================
+ Coverage   68.39%   68.97%   +0.58%     
==========================================
  Files           9        9              
  Lines        1411     1441      +30     
  Branches      462      467       +5     
==========================================
+ Hits          965      994      +29     
- Misses        442      444       +2     
+ Partials        4        3       -1     
Flag Coverage Δ
library 68.97% <81.81%> (+0.58%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/utils/type-bridge.ts 78.71% <81.81%> (+0.63%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

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

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 is a significant and valuable update. It adds many missing platform-specific fields to the IAP types, improving parity between iOS and Android. The refactoring to remove as any casts and introduce a typed object for oneTimePurchaseOfferDetailsAndroid greatly enhances type safety. The addition of the global DataModalContext in the example apps is a nice touch for improving the developer experience.

My review focuses on a few areas in the example apps:

  • A regression in purchase-flow.tsx where filtering for owned products was removed.
  • An opportunity to refactor subscription-flow.tsx to reduce prop drilling and improve maintainability.
  • A couple of minor type safety improvements and inconsistencies in the example app UI.

Overall, the core library changes are excellent. The feedback is mostly centered on making the example app code more robust and consistent.

Comment on lines +73 to +74
const visibleProducts = products;
const hasHiddenNonConsumables = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The logic to filter out and hide already-owned non-consumable products from the list of available products has been removed. This appears to be a regression in the example app's functionality, as it will now show products that the user has already purchased. Was this removal intentional? If not, please consider re-introducing this filtering logic.

setPurchaseDetailsTarget(lastPurchase);
setPurchaseDetailsVisible(true);
}}
onPress={() => {}}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The onPress handler for the PurchaseSummaryRow displaying the lastPurchase is currently a no-op (() => {}). For consistency with the changes in available-purchases.tsx, this should use the new DataModalContext to display the purchase details. You would need to get showData from useDataModal in the container and pass it down as a prop.

setPurchaseDetailsVisible(true);
}}
/>
<PurchaseSummaryRow purchase={lastPurchase} onPress={() => {}} />
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The onPress handler for the lastPurchase is a no-op. For consistency with other parts of the app, consider using the new DataModalContext to show the purchase details. You would need to get showData from useDataModal in the container and pass it down as a prop.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt (1)

521-529: Bug: remove*Listener clears all listeners

Both removePurchaseUpdatedListener and removePurchaseErrorListener call clear(), removing unrelated listeners. Remove only the provided listener.

Apply this diff:

-    override fun removePurchaseUpdatedListener(listener: (purchase: NitroPurchase) -> Unit) {
-        // Note: Kotlin doesn't have easy closure comparison, so we'll clear all listeners
-        purchaseUpdatedListeners.clear()
-    }
+    override fun removePurchaseUpdatedListener(listener: (purchase: NitroPurchase) -> Unit) {
+        purchaseUpdatedListeners.remove(listener)
+    }
 
-    override fun removePurchaseErrorListener(listener: (error: NitroPurchaseResult) -> Unit) {
-        // Note: Kotlin doesn't have easy closure comparison, so we'll clear all listeners
-        purchaseErrorListeners.clear()
-    }
+    override fun removePurchaseErrorListener(listener: (error: NitroPurchaseResult) -> Unit) {
+        purchaseErrorListeners.remove(listener)
+    }
🧹 Nitpick comments (6)
example-expo/app/purchase-flow.tsx (1)

309-364: Optional: Consider using the shared DataModal instead of a local Modal

To align examples with the new global DataModalProvider, route Product Details through the shared modal for consistency and reuse.

src/specs/RnIap.nitro.ts (2)

304-313: Fix JSDoc: return type mismatch for requestPurchase

Comment says Promise but signature returns Promise. Update docs to prevent confusion for API users.

Apply this diff to the doc:

   /**
    * Request a purchase (unified method for both platforms)
    * ⚠️ Important: This is an event-based operation, not promise-based.
    * Listen for events through purchaseUpdatedListener or purchaseErrorListener.
    * @param request - Platform-specific purchase request parameters
-   * @returns Promise<void> - Always returns void, listen for events instead
+   * @returns Promise<RequestPurchaseResult> - Do not rely on this for flow; listen to purchase events
    */

182-190: Document micros semantics for Android pricing fields

Clarify that priceAmountMicros is a string representing micro-units (1e-6), matching Google Play Billing. Helps downstream consumers parse consistently.

Apply this diff to add inline docs:

 export interface NitroOneTimePurchaseOfferDetail {
-  formattedPrice: string;
-  priceAmountMicros: string;
-  priceCurrencyCode: string;
+  /** Localized formatted price (e.g., "$1.99") */
+  formattedPrice: string;
+  /** Microunits as string (e.g., "1990000" == 1.99) */
+  priceAmountMicros: string;
+  /** ISO 4217 currency code (e.g., "USD") */
+  priceCurrencyCode: string;
 }
android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt (1)

577-591: Unused helper: createPurchaseErrorResult

Not referenced; consider removing to reduce surface and confusion with toErrorResult().

example-expo/app/subscription-flow.tsx (2)

621-623: Nit: avoid parseInt on a number-typed field

subscriptionPeriodNumberIOS is numeric; prefer Number() to avoid string parsing semantics.

Apply this diff:

-        const periodNum = parseInt(periodNumber, 10);
+        const periodNum = Number(periodNumber);

486-500: Optional: replace magic number proration mode with a named constant

If available from your types/SDK, use a named enum/constant (e.g., IMMEDIATE_WITHOUT_PRORATION) instead of 5 for readability.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50b2436 and 4f04806.

⛔ Files ignored due to path filters (1)
  • example/ios/Podfile.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt (5 hunks)
  • example-expo/app/_layout.tsx (1 hunks)
  • example-expo/app/available-purchases.tsx (3 hunks)
  • example-expo/app/purchase-flow.tsx (11 hunks)
  • example-expo/app/subscription-flow.tsx (14 hunks)
  • example-expo/contexts/DataModalContext.tsx (1 hunks)
  • example-expo/scripts/copy-screens.sh (1 hunks)
  • example/App.tsx (1 hunks)
  • example/__tests__/screens/AvailablePurchases.test.tsx (6 hunks)
  • example/screens/AvailablePurchases.tsx (3 hunks)
  • example/src/contexts/DataModalContext.tsx (1 hunks)
  • ios/RnIapHelper.swift (2 hunks)
  • src/specs/RnIap.nitro.ts (6 hunks)
  • src/utils/type-bridge.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use type-only imports when importing types (import type).

Files:

  • example/__tests__/screens/AvailablePurchases.test.tsx
  • example/src/contexts/DataModalContext.tsx
  • example/screens/AvailablePurchases.tsx
  • example-expo/app/_layout.tsx
  • example-expo/contexts/DataModalContext.tsx
  • example-expo/app/available-purchases.tsx
  • src/specs/RnIap.nitro.ts
  • example/App.tsx
  • example-expo/app/purchase-flow.tsx
  • example-expo/app/subscription-flow.tsx
  • src/utils/type-bridge.ts
{src,example,example-expo}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{src,example,example-expo}/**/*.{ts,tsx}: When declaring API params/results in TS modules, import canonical types from src/types.ts rather than creating custom interfaces.
Handle errors using the shared utilities (parseErrorStringToJsonObj, isUserCancelledError) and standardized ErrorCode enum.

Files:

  • example/__tests__/screens/AvailablePurchases.test.tsx
  • example/src/contexts/DataModalContext.tsx
  • example/screens/AvailablePurchases.tsx
  • example-expo/app/_layout.tsx
  • example-expo/contexts/DataModalContext.tsx
  • example-expo/app/available-purchases.tsx
  • src/specs/RnIap.nitro.ts
  • example/App.tsx
  • example-expo/app/purchase-flow.tsx
  • example-expo/app/subscription-flow.tsx
  • src/utils/type-bridge.ts
{src,example,example-expo}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Platform.OS checks for platform-specific logic in React Native code.

Files:

  • example/__tests__/screens/AvailablePurchases.test.tsx
  • example/src/contexts/DataModalContext.tsx
  • example/screens/AvailablePurchases.tsx
  • example-expo/app/_layout.tsx
  • example-expo/contexts/DataModalContext.tsx
  • example-expo/app/available-purchases.tsx
  • src/specs/RnIap.nitro.ts
  • example/App.tsx
  • example-expo/app/purchase-flow.tsx
  • example-expo/app/subscription-flow.tsx
  • src/utils/type-bridge.ts
{example,example-expo}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

In useIAP-based UI code, do not expect return values from methods that update internal state (e.g., fetchProducts, requestPurchase); consume state from the hook. Only the documented exceptions return values.

Files:

  • example/__tests__/screens/AvailablePurchases.test.tsx
  • example/src/contexts/DataModalContext.tsx
  • example/screens/AvailablePurchases.tsx
  • example-expo/app/_layout.tsx
  • example-expo/contexts/DataModalContext.tsx
  • example-expo/app/available-purchases.tsx
  • example/App.tsx
  • example-expo/app/purchase-flow.tsx
  • example-expo/app/subscription-flow.tsx
ios/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Follow the native class function ordering in Swift: properties/init; public cross-platform methods; platform-specific public methods; event listener methods; private helpers.

Files:

  • ios/RnIapHelper.swift
android/src/main/java/com/margelo/nitro/iap/**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

android/src/main/java/com/margelo/nitro/iap/**/*.kt: Follow the native class function ordering in Kotlin: properties/init; public cross-platform methods; platform-specific public methods; event listener methods; private helpers.
On Android, rely on automatic service reconnection; only check that the BillingClient exists, not connection state.

Files:

  • android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
src/specs/RnIap.nitro.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Define the Nitro interface (ALL native method declarations) in src/specs/RnIap.nitro.ts as the contract to native code.

Files:

  • src/specs/RnIap.nitro.ts
src/specs/**/*.nitro.ts

📄 CodeRabbit inference engine (CLAUDE.md)

After modifying any .nitro.ts interface files, regenerate Nitro bridge files (yarn specs).

Files:

  • src/specs/RnIap.nitro.ts
🧠 Learnings (3)
📚 Learning: 2025-09-24T00:58:14.901Z
Learnt from: CR
PR: hyochan/react-native-iap#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-24T00:58:14.901Z
Learning: Applies to {example,example-expo}/**/*.{ts,tsx,js,jsx} : In useIAP-based UI code, do not expect return values from methods that update internal state (e.g., fetchProducts, requestPurchase); consume state from the hook. Only the documented exceptions return values.

Applied to files:

  • example/screens/AvailablePurchases.tsx
  • example-expo/app/available-purchases.tsx
  • example-expo/app/purchase-flow.tsx
  • example-expo/app/subscription-flow.tsx
📚 Learning: 2025-09-24T00:58:14.901Z
Learnt from: CR
PR: hyochan/react-native-iap#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-24T00:58:14.901Z
Learning: Applies to android/src/main/java/com/margelo/nitro/iap/**/*.kt : Follow the native class function ordering in Kotlin: properties/init; public cross-platform methods; platform-specific public methods; event listener methods; private helpers.

Applied to files:

  • android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
📚 Learning: 2025-09-24T00:58:14.901Z
Learnt from: CR
PR: hyochan/react-native-iap#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-24T00:58:14.901Z
Learning: Applies to src/specs/RnIap.nitro.ts : Define the Nitro interface (ALL native method declarations) in src/specs/RnIap.nitro.ts as the contract to native code.

Applied to files:

  • src/specs/RnIap.nitro.ts
🧬 Code graph analysis (9)
example/__tests__/screens/AvailablePurchases.test.tsx (1)
example/screens/AvailablePurchases.tsx (1)
  • AvailablePurchases (21-376)
example/screens/AvailablePurchases.tsx (1)
example/src/contexts/DataModalContext.tsx (1)
  • useDataModal (103-109)
example-expo/app/_layout.tsx (1)
example-expo/contexts/DataModalContext.tsx (1)
  • DataModalProvider (23-101)
example-expo/app/available-purchases.tsx (1)
example-expo/contexts/DataModalContext.tsx (1)
  • useDataModal (103-109)
android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt (1)
src/specs/RnIap.nitro.ts (1)
  • NitroOneTimePurchaseOfferDetail (185-189)
example/App.tsx (1)
example/src/contexts/DataModalContext.tsx (1)
  • DataModalProvider (23-101)
example-expo/app/purchase-flow.tsx (2)
src/hooks/useIAP.ts (1)
  • useIAP (90-397)
src/index.ts (3)
  • useIAP (104-104)
  • getStorefront (564-595)
  • fetchProducts (315-446)
example-expo/app/subscription-flow.tsx (6)
src/types.ts (4)
  • ProductSubscription (276-276)
  • Purchase (332-332)
  • ActiveSubscription (6-17)
  • PurchaseError (368-372)
android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt (3)
  • getAvailablePurchases (377-414)
  • requestPurchase (240-374)
  • fetchProducts (178-236)
src/index.ts (5)
  • getAvailablePurchases (461-517)
  • requestPurchase (879-1000)
  • useIAP (104-104)
  • getActiveSubscriptions (1470-1543)
  • fetchProducts (315-446)
ios/HybridRnIap.swift (3)
  • getAvailablePurchases (227-248)
  • requestPurchase (143-225)
  • fetchProducts (80-141)
src/hooks/useIAP.ts (1)
  • useIAP (90-397)
example-expo/constants/products.ts (1)
  • SUBSCRIPTION_PRODUCT_IDS (17-17)
src/utils/type-bridge.ts (1)
src/types.ts (1)
  • Product (214-214)
🪛 GitHub Actions: CI - Example Expo
example-expo/app/subscription-flow.tsx

[warning] 35-35: eslint: @typescript-eslint/array-type - Array type using 'Array' is forbidden. Use 'T[]' instead.


[warning] 41-41: eslint: @typescript-eslint/array-type - Array type using 'Array' is forbidden. Use 'T[]' instead.


[error] 80-80: react/display-name: Component definition is missing display name. (eslint rule react/display-name). Command: expo lint

🪛 GitHub Check: example-expo
example-expo/app/subscription-flow.tsx

[failure] 80-80:
Component definition is missing display name


[warning] 41-41:
Array type using 'Array' is forbidden. Use 'T[]' instead


[warning] 35-35:
Array type using 'Array' is forbidden. Use 'T[]' instead


[warning] 527-527:
React Hook useCallback has missing dependencies: 'cachedAvailablePurchases' and 'setCachedAvailablePurchases'. Either include them or remove the dependency array. If 'setCachedAvailablePurchases' changes too often, find the parent component that defines it and wrap that definition in useCallback

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-android
  • GitHub Check: build-ios
🔇 Additional comments (7)
example-expo/app/purchase-flow.tsx (2)

443-454: LGTM: Robust storefront fetch flow with safe fallbacks

Good async handling, disabled button state, and normalization to null on blanks. Logs on failure without breaking UI.


421-437: User‑cancel path handled correctly

Explicitly checks ErrorCode.UserCancelled and short‑circuits UX messaging. Nice.

android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt (4)

660-666: LGTM: one‑time offer typed mapping

Nice conversion to NitroOneTimePurchaseOfferDetail; keeps types aligned with the spec.


705-709: LGTM: nameAndroid propagation

Correctly reads nameAndroid from both ProductAndroid and ProductSubscriptionAndroid.


724-742: LGTM: Extended product payload (JSON offers + one‑time offer details)

JSON serialization for subscriptionOfferDetailsAndroid and typed oneTimePurchaseOfferDetailsAndroid look good.


793-794: LGTM: developerPayloadAndroid exposed

Mapping developerPayloadAndroid completes parity with upstream OpenIAP.

example-expo/app/subscription-flow.tsx (1)

1132-1139: Good: refresh both active subs and purchases after success

Caching getAvailablePurchases and refreshing state aligns with the hook’s event flow.

Copy link

@coderabbitai coderabbitai bot left a comment

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)
example/screens/SubscriptionFlow.tsx (1)

61-190: Name the memoized component to satisfy react/display-name

expo lint still fails because this React.memo call receives an anonymous arrow. Please switch to a named function when passing it to React.memo (you can keep the explicit displayName). Example fix:

-const PlanChangeControls = React.memo<PlanChangeControlsProps>(
-  ({
-    activeSubscriptions,
-    handlePlanChange,
-    isProcessing,
-    lastPurchasedPlan,
-  }) => {
+const PlanChangeControls = React.memo(function PlanChangeControls({
+  activeSubscriptions,
+  handlePlanChange,
+  isProcessing,
+  lastPurchasedPlan,
+}: PlanChangeControlsProps) {
@@
-  },
-);
+});
♻️ Duplicate comments (1)
example-expo/app/subscription-flow.tsx (1)

61-194: Regenerate this copy after fixing the PlanChangeControls name

This generated file still has the anonymous React.memo wrapper, so the react/display-name lint failure will persist here too. After applying the named-function fix in example/screens/SubscriptionFlow.tsx, rerun the copy step (e.g., yarn copy-screens) so this file inherits the change.

🧹 Nitpick comments (1)
example/screens/SubscriptionFlow.tsx (1)

84-87: Drop the as any when sorting by transactionDate

ActiveSubscription already exposes transactionDate, so we can compare it directly and keep the function fully typed:

-      const sortedSubs = [...premiumSubs].sort((a, b) => {
-        const dateA = (a as any).transactionDate || 0;
-        const dateB = (b as any).transactionDate || 0;
+      const sortedSubs = [...premiumSubs].sort((a, b) => {
+        const dateA = a.transactionDate ?? 0;
+        const dateB = b.transactionDate ?? 0;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f04806 and 82836ac.

📒 Files selected for processing (3)
  • .github/workflows/ci-example-expo.yml (1 hunks)
  • example-expo/app/subscription-flow.tsx (13 hunks)
  • example/screens/SubscriptionFlow.tsx (6 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use type-only imports when importing types (import type).

Files:

  • example-expo/app/subscription-flow.tsx
  • example/screens/SubscriptionFlow.tsx
{src,example,example-expo}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{src,example,example-expo}/**/*.{ts,tsx}: When declaring API params/results in TS modules, import canonical types from src/types.ts rather than creating custom interfaces.
Handle errors using the shared utilities (parseErrorStringToJsonObj, isUserCancelledError) and standardized ErrorCode enum.

Files:

  • example-expo/app/subscription-flow.tsx
  • example/screens/SubscriptionFlow.tsx
{src,example,example-expo}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Platform.OS checks for platform-specific logic in React Native code.

Files:

  • example-expo/app/subscription-flow.tsx
  • example/screens/SubscriptionFlow.tsx
{example,example-expo}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

In useIAP-based UI code, do not expect return values from methods that update internal state (e.g., fetchProducts, requestPurchase); consume state from the hook. Only the documented exceptions return values.

Files:

  • example-expo/app/subscription-flow.tsx
  • example/screens/SubscriptionFlow.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-24T00:58:14.901Z
Learnt from: CR
PR: hyochan/react-native-iap#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-24T00:58:14.901Z
Learning: Applies to {example,example-expo}/**/*.{ts,tsx,js,jsx} : In useIAP-based UI code, do not expect return values from methods that update internal state (e.g., fetchProducts, requestPurchase); consume state from the hook. Only the documented exceptions return values.

Applied to files:

  • example-expo/app/subscription-flow.tsx
🧬 Code graph analysis (2)
example-expo/app/subscription-flow.tsx (5)
src/types.ts (4)
  • Purchase (332-332)
  • ActiveSubscription (6-17)
  • ProductSubscriptionAndroid (278-292)
  • PurchaseError (368-372)
android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt (2)
  • getAvailablePurchases (377-414)
  • requestPurchase (240-374)
src/index.ts (4)
  • getAvailablePurchases (461-517)
  • requestPurchase (879-1000)
  • useIAP (104-104)
  • getActiveSubscriptions (1470-1543)
src/hooks/useIAP.ts (1)
  • useIAP (90-397)
example-expo/constants/products.ts (1)
  • SUBSCRIPTION_PRODUCT_IDS (17-17)
example/screens/SubscriptionFlow.tsx (1)
src/types.ts (1)
  • ProductSubscriptionAndroid (278-292)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-ios
  • GitHub Check: build-android

Copy link

@coderabbitai coderabbitai bot left a comment

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 (2)
example-expo/app/subscription-flow.tsx (2)

1150-1174: Add getAvailablePurchases to this effect’s deps

This effect invokes getAvailablePurchases() but the dependency array omits it, tripping react-hooks/exhaustive-deps. Append it so lint passes.

-  }, [connected, fetchProducts]);
+  }, [connected, fetchProducts, getAvailablePurchases]);

1175-1210: Also list getAvailablePurchases here

Same lint rule applies to handleRefreshStatus; include the function in the dependency list so ESLint stops flagging it.

-  }, [connected, getActiveSubscriptions, isCheckingStatus]);
+  }, [connected, getActiveSubscriptions, getAvailablePurchases, isCheckingStatus]);
♻️ Duplicate comments (3)
example-expo/app/subscription-flow.tsx (3)

945-959: Wire the latest purchase row into the DataModal

PurchaseSummaryRow still has a no-op handler, so tapping the latest purchase yields nothing. Please reuse useDataModal().showData here (as noted earlier) so the Expo flow matches the rest of the app.


61-191: Name the memoized component so react/display-name stops failing

Expo lint still flags this anonymous arrow you pass to React.memo. Please switch to a named function (per the earlier review) so the rule recognizes the display name.

-const PlanChangeControls = React.memo<PlanChangeControlsProps>(
-  ({
-    activeSubscriptions,
-    handlePlanChange,
-    isProcessing,
-    lastPurchasedPlan,
-  }) => {
+const PlanChangeControls = React.memo(function PlanChangeControls({
+  activeSubscriptions,
+  handlePlanChange,
+  isProcessing,
+  lastPurchasedPlan,
+}: PlanChangeControlsProps) {-  },
-);
+});

674-679: Drop the new (a as any) casts

ActiveSubscription already exposes transactionDate, so the fresh casts undo the type-safety work in this PR. Read the field directly.

-                  const sortedPremiumSubs = [...premiumSubs].sort((a, b) => {
-                    const dateA = (a as any).transactionDate || 0;
-                    const dateB = (b as any).transactionDate || 0;
+                  const sortedPremiumSubs = [...premiumSubs].sort((a, b) => {
+                    const dateA = a.transactionDate ?? 0;
+                    const dateB = b.transactionDate ?? 0;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 82836ac and 41c8a7f.

📒 Files selected for processing (6)
  • example-expo/app/subscription-flow.tsx (13 hunks)
  • example-expo/contexts/DataModalContext.tsx (1 hunks)
  • example/__tests__/screens/AvailablePurchases.test.tsx (6 hunks)
  • example/screens/SubscriptionFlow.tsx (7 hunks)
  • example/src/contexts/DataModalContext.tsx (1 hunks)
  • src/utils/type-bridge.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use type-only imports when importing types (import type).

Files:

  • example/__tests__/screens/AvailablePurchases.test.tsx
  • example/src/contexts/DataModalContext.tsx
  • example/screens/SubscriptionFlow.tsx
  • src/utils/type-bridge.ts
  • example-expo/app/subscription-flow.tsx
  • example-expo/contexts/DataModalContext.tsx
{src,example,example-expo}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{src,example,example-expo}/**/*.{ts,tsx}: When declaring API params/results in TS modules, import canonical types from src/types.ts rather than creating custom interfaces.
Handle errors using the shared utilities (parseErrorStringToJsonObj, isUserCancelledError) and standardized ErrorCode enum.

Files:

  • example/__tests__/screens/AvailablePurchases.test.tsx
  • example/src/contexts/DataModalContext.tsx
  • example/screens/SubscriptionFlow.tsx
  • src/utils/type-bridge.ts
  • example-expo/app/subscription-flow.tsx
  • example-expo/contexts/DataModalContext.tsx
{src,example,example-expo}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Platform.OS checks for platform-specific logic in React Native code.

Files:

  • example/__tests__/screens/AvailablePurchases.test.tsx
  • example/src/contexts/DataModalContext.tsx
  • example/screens/SubscriptionFlow.tsx
  • src/utils/type-bridge.ts
  • example-expo/app/subscription-flow.tsx
  • example-expo/contexts/DataModalContext.tsx
{example,example-expo}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

In useIAP-based UI code, do not expect return values from methods that update internal state (e.g., fetchProducts, requestPurchase); consume state from the hook. Only the documented exceptions return values.

Files:

  • example/__tests__/screens/AvailablePurchases.test.tsx
  • example/src/contexts/DataModalContext.tsx
  • example/screens/SubscriptionFlow.tsx
  • example-expo/app/subscription-flow.tsx
  • example-expo/contexts/DataModalContext.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-24T00:58:14.901Z
Learnt from: CR
PR: hyochan/react-native-iap#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-24T00:58:14.901Z
Learning: Applies to {example,example-expo}/**/*.{ts,tsx,js,jsx} : In useIAP-based UI code, do not expect return values from methods that update internal state (e.g., fetchProducts, requestPurchase); consume state from the hook. Only the documented exceptions return values.

Applied to files:

  • example-expo/app/subscription-flow.tsx
🧬 Code graph analysis (4)
example/__tests__/screens/AvailablePurchases.test.tsx (1)
example/src/contexts/DataModalContext.tsx (1)
  • DataModalProvider (30-122)
example/screens/SubscriptionFlow.tsx (1)
src/types.ts (1)
  • ProductSubscriptionAndroid (278-292)
src/utils/type-bridge.ts (1)
src/types.ts (1)
  • Product (214-214)
example-expo/app/subscription-flow.tsx (4)
src/types.ts (3)
  • Purchase (332-332)
  • ActiveSubscription (6-17)
  • ProductSubscriptionAndroid (278-292)
src/index.ts (5)
  • getAvailablePurchases (461-517)
  • requestPurchase (879-1000)
  • useIAP (104-104)
  • getActiveSubscriptions (1470-1543)
  • fetchProducts (315-446)
src/hooks/useIAP.ts (1)
  • useIAP (90-397)
example-expo/constants/products.ts (1)
  • SUBSCRIPTION_PRODUCT_IDS (17-17)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build-android
  • GitHub Check: build-ios
  • GitHub Check: example-expo
🔇 Additional comments (7)
example/screens/SubscriptionFlow.tsx (7)

57-63: LGTM! Explicit generic type improves type safety.

The explicit generic signature React.memo<PlanChangeControlsProps> provides better type checking and IDE support.


81-82: LGTM! Safe property access with nullish coalescing.

Using ?? 0 provides a safe fallback when transactionDate is null or undefined.


189-189: LGTM! DisplayName aids debugging.

Setting displayName on memoized components improves React DevTools output.


360-360: LGTM! Appropriate cast for Android-specific subscription details.

The cast to ProductSubscriptionAndroid is necessary to access subscriptionOfferDetailsAndroid, which is Android-specific per the type definition.


506-513: LGTM! Complete useCallback dependency array.

The expanded dependencies correctly include cachedAvailablePurchases and setCachedAvailablePurchases, which are both used within the callback (lines 378-383).


1238-1242: LGTM! Type-safe casts for Android subscription offers.

The casts to ProductSubscriptionAndroid are appropriately guarded by first checking for the existence of subscriptionOfferDetailsAndroid (line 1237), ensuring type safety before accessing Android-specific properties.


21-21: Use type-only import for ProductSubscriptionAndroid.

Per coding guidelines, import types using type-only syntax.

Apply this diff:

   type ActiveSubscription,
   type ProductSubscription,
-  type ProductSubscriptionAndroid,
+  type ProductSubscriptionAndroid,
   type Purchase,

Wait, let me check the actual line again. The import already uses type keyword. Let me re-examine.

Actually, reviewing line 21 again: type ProductSubscriptionAndroid, - this already uses the type keyword prefix for the individual import. This is correct per the guideline. No issue here.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
example/screens/SubscriptionFlow.tsx (1)

278-521: Include getAvailablePurchases in the dependency array.

The handlePlanChange callback calls the imported getAvailablePurchases function at line 388, but it is not listed in the dependency array at lines 509-516. While getAvailablePurchases is a stable import, including it in the dependency array satisfies the react-hooks/exhaustive-deps rule and makes the dependencies explicit.

Apply this diff to add getAvailablePurchases to the dependency array:

     [
       subscriptions,
       activeSubscriptions,
       setIsProcessing,
       setPurchaseResult,
       cachedAvailablePurchases,
       setCachedAvailablePurchases,
+      getAvailablePurchases,
     ],
🧹 Nitpick comments (1)
example/screens/SubscriptionFlow.tsx (1)

699-699: Use ExtendedActiveSubscription instead of any for better type safety.

Line 699 uses sub: any in the map callback, which defeats the type safety improvements in this PR. Since ExtendedActiveSubscription is already defined (lines 45-50), use it here for consistency.

Apply this diff:

-              return subsToShow.map((sub: any, index: number) => {
+              return subsToShow.map((sub: ExtendedActiveSubscription, index: number) => {

Then, remove the cast at line 719:

-                  const extendedSub = sub as ExtendedActiveSubscription;
                   console.log(
                     'Product ID:',
                     sub.productId,
                     'Is Upgraded?:',
-                    extendedSub.isUpgradedIOS,
+                    sub.isUpgradedIOS,
                   );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 41c8a7f and 493f4cf.

📒 Files selected for processing (3)
  • example-expo/app/subscription-flow.tsx (13 hunks)
  • example/__tests__/screens/AvailablePurchases.test.tsx (6 hunks)
  • example/screens/SubscriptionFlow.tsx (12 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • example/tests/screens/AvailablePurchases.test.tsx
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use type-only imports when importing types (import type).

Files:

  • example-expo/app/subscription-flow.tsx
  • example/screens/SubscriptionFlow.tsx
{src,example,example-expo}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

{src,example,example-expo}/**/*.{ts,tsx}: When declaring API params/results in TS modules, import canonical types from src/types.ts rather than creating custom interfaces.
Handle errors using the shared utilities (parseErrorStringToJsonObj, isUserCancelledError) and standardized ErrorCode enum.

Files:

  • example-expo/app/subscription-flow.tsx
  • example/screens/SubscriptionFlow.tsx
{src,example,example-expo}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Platform.OS checks for platform-specific logic in React Native code.

Files:

  • example-expo/app/subscription-flow.tsx
  • example/screens/SubscriptionFlow.tsx
{example,example-expo}/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

In useIAP-based UI code, do not expect return values from methods that update internal state (e.g., fetchProducts, requestPurchase); consume state from the hook. Only the documented exceptions return values.

Files:

  • example-expo/app/subscription-flow.tsx
  • example/screens/SubscriptionFlow.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-24T00:58:14.901Z
Learnt from: CR
PR: hyochan/react-native-iap#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-24T00:58:14.901Z
Learning: Applies to {example,example-expo}/**/*.{ts,tsx,js,jsx} : In useIAP-based UI code, do not expect return values from methods that update internal state (e.g., fetchProducts, requestPurchase); consume state from the hook. Only the documented exceptions return values.

Applied to files:

  • example-expo/app/subscription-flow.tsx
🧬 Code graph analysis (2)
example-expo/app/subscription-flow.tsx (4)
src/types.ts (3)
  • Purchase (332-332)
  • ActiveSubscription (6-17)
  • ProductSubscriptionAndroid (278-292)
src/index.ts (4)
  • getAvailablePurchases (461-517)
  • requestPurchase (879-1000)
  • useIAP (104-104)
  • getActiveSubscriptions (1470-1543)
src/hooks/useIAP.ts (1)
  • useIAP (90-397)
example-expo/constants/products.ts (1)
  • SUBSCRIPTION_PRODUCT_IDS (17-17)
example/screens/SubscriptionFlow.tsx (1)
src/types.ts (2)
  • ActiveSubscription (6-17)
  • ProductSubscriptionAndroid (278-292)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build-ios
  • GitHub Check: build-android
🔇 Additional comments (10)
example/screens/SubscriptionFlow.tsx (9)

21-21: LGTM: Type-only import used correctly.

The import of ProductSubscriptionAndroid correctly uses the type keyword for type-only imports.


30-37: LGTM: ExtendedPurchase type definition is appropriate.

The type definition appropriately extends Purchase with optional Android-specific fields needed for plan-change logic.


39-50: LGTM: ExtendedActiveSubscription type is well-documented.

The type definition appropriately extends ActiveSubscription with optional fields for plan detection, and the inline comments clearly explain the purpose of each field.


64-192: LGTM: PlanChangeControls component correctly addresses past review feedback.

The component now uses a named function inside React.memo, resolving the ESLint react/display-name issue. The sorting logic at lines 91-92 correctly uses nullish coalescing (?? 0) instead of as any, improving type safety as recommended in previous reviews.


214-232: LGTM: SubscriptionFlowProps type accurately reflects new functionality.

The expanded props correctly support the new plan-change and caching features with appropriate types.


836-841: LGTM: PlanChangeControls is correctly integrated.

The component is properly integrated with all required props.


1036-1079: LGTM: Plan detection logic in onPurchaseSuccess is well-structured.

The callback correctly detects the purchased plan for both iOS (via productId) and Android (via offerToken mapping), and the cast to ProductSubscriptionAndroid at line 1060 is safe within the Android-specific branch.


1117-1126: LGTM: Refresh logic correctly caches available purchases.

The callback appropriately refreshes both active subscriptions and available purchases after a successful purchase, and caches the latter for use in plan-change logic.


1184-1191: LGTM: Refresh status logic correctly updates cache.

The refresh status handler appropriately updates the cached available purchases, ensuring the cache stays fresh.

example-expo/app/subscription-flow.tsx (1)

1-4: This file is auto-generated from example/screens/SubscriptionFlow.tsx.

As noted in the header comment (lines 1-3), this file is automatically copied during postinstall. Any changes should be made to the source file example/screens/SubscriptionFlow.tsx, not here. Review comments for this file apply to the source file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🛠 bugfix All kinds of bug fixes 🎧 essential ❄️ types Typing issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant