Skip to content
This repository was archived by the owner on Apr 26, 2026. It is now read-only.

feat(ios): add requestPurchaseWithAdvancedCommerce support#3106

Closed
hlus wants to merge 8 commits into
hyochan:mainfrom
hlus:feature/ios-request-purchase-with-advanced-commerce
Closed

feat(ios): add requestPurchaseWithAdvancedCommerce support#3106
hlus wants to merge 8 commits into
hyochan:mainfrom
hlus:feature/ios-request-purchase-with-advanced-commerce

Conversation

@hlus

@hlus hlus commented Dec 16, 2025

Copy link
Copy Markdown

Description

Adds requestPurchaseWithAdvancedCommerce() method for iOS that enables passing custom advanced commerce data during StoreKit 2 purchase flows using Product.PurchaseOption.custom.

Changes

  • Added NitroAdvancedCommercePurchaseResult type to Nitro spec
  • Implemented requestPurchaseWithAdvancedCommerceIOS in Swift using StoreKit 2
  • Added TypeScript API wrapper with platform checks and error handling
  • Updated documentation in docs/docs/api/methods/core-methods.md
  • Generated Nitro bridge files

Requirements

  • iOS 15.0+ (StoreKit 2)

Testing

  • ✅ Type checking passes (yarn typecheck:lib)
  • ✅ Linting passes (yarn lint --fix)
  • ✅ Nitro bridge files generated (yarn specs)
  • ⏳ Manual testing on iOS device/simulator (pending)

Summary by CodeRabbit

  • New Features

    • iOS-only advanced commerce purchase API (iOS 15+) that accepts advanced commerce JSON and returns a Promise result: success, transactionId, productId, purchaseDate. Includes an optional automatic-transaction-finish flag (defaults to true).
  • Documentation

    • Docs updated with usage example, JSON guidance, error notes (including user-cancelled) and platform/version requirement.
  • Tests

    • Added tests and mocks covering success, error paths, platform availability, parameter propagation, and result mapping.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Dec 16, 2025

Copy link
Copy Markdown

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 an iOS‑only advanced‑commerce purchase API across docs, TypeScript specs, JS public API, unit tests, and the iOS native bridge using StoreKit 2; the API returns a Promise resolving to { success, transactionId, productId, purchaseDate } and requires iOS 15+.

Changes

Cohort / File(s) Summary
Documentation
docs/docs/api/methods/core-methods.md
Adds docs for requestPurchaseWithAdvancedCommerce() including signature, returned object shape, example usage, error handling (e.g., UserCancelled), JSON note for advancedCommerceData, and iOS 15+ requirement.
Type Definitions / Specs
src/specs/RnIap.nitro.ts
Adds NitroAdvancedCommercePurchaseResult interface and requestPurchaseWithAdvancedCommerceIOS(productId: string, advancedCommerceData: string, andDangerouslyFinishTransactionAutomatically?: boolean): Promise<NitroAdvancedCommercePurchaseResult>; to the RnIap spec.
Public JS API
src/index.ts
Adds AdvancedCommercePurchaseResult interface and exported requestPurchaseWithAdvancedCommerce(productId, advancedCommerceData, andDangerouslyFinishTransactionAutomatically = true) with iOS guard, calls native bridge, maps native result, and normalizes/throws PurchaseError including productId.
Tests
src/__tests__/index.test.ts
Adds unit tests and mock native method for the new API: iOS success path, non‑iOS error, error propagation, error normalization with productId, result mapping, and third-parameter propagation checks.
Native iOS Implementation
ios/HybridRnIap.swift
Implements requestPurchaseWithAdvancedCommerceIOS(...) using StoreKit 2: iOS 15+ check, ensure connection, fetch product, build JSON payload with advancedCommerceData, start purchase with custom option, handle verified/unverified/pending/cancelled outcomes, optionally finish transaction, map/log errors, and return NitroAdvancedCommercePurchaseResult.
sequenceDiagram
    participant JS as JavaScript
    participant TS as TypeScript API
    participant Bridge as Nitro Bridge
    participant Native as iOS Native
    participant SK2 as StoreKit2

    JS->>TS: requestPurchaseWithAdvancedCommerce(productId, data, finishFlag)
    TS->>TS: platform check (iOS)
    TS->>Bridge: requestPurchaseWithAdvancedCommerceIOS(productId, data, finishFlag)
    Bridge->>Native: invoke native method

    Note over Native: check iOS 15+, ensure connection, fetch product
    Native->>SK2: start purchase with advancedCommerce JSON payload

    alt Transaction verified
        SK2-->>Native: verified transaction
        Native->>SK2: finish(transaction) [if finishFlag]
        Native-->>Bridge: return NitroAdvancedCommercePurchaseResult
    else Transaction unverified
        SK2-->>Native: unverified
        Native-->>Bridge: throw verification error
    else User cancelled
        SK2-->>Native: cancelled
        Native-->>Bridge: throw userCancelled error
    else Pending/unknown
        SK2-->>Native: pending/unknown
        Native-->>Bridge: throw pending/unknown error
    end

    Bridge-->>TS: resolve/reject mapped result/error
    TS-->>JS: Promise resolves { success, transactionId, productId, purchaseDate } or rejects
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Review StoreKit 2 branching, transaction verification, and finish logic in ios/HybridRnIap.swift.
  • Verify JSON serialization and exact payload shape for advancedCommerceData.
  • Confirm native-to-JS result mapping and PurchaseError normalization in src/index.ts.
  • Validate tests in src/__tests__/index.test.ts for platform gating and mock fidelity.

Possibly related PRs

Suggested labels

🎯 feature, 📖 documentation, 📱 iOS, ❄️ types

Poem

🐰 I slipped JSON into my tiny sack,
Hopped to StoreKit and brought the purchase back.
A promise popped open with receipt in hand,
TransactionId bright like carrots in the sand,
Hooray — the meadow bought what I planned! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% 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 'feat(ios): add requestPurchaseWithAdvancedCommerce support' accurately describes the main change—adding a new iOS method for advanced commerce purchases with StoreKit 2 integration.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d508c0 and 1a54a1b.

📒 Files selected for processing (4)
  • ios/HybridRnIap.swift (2 hunks)
  • src/__tests__/index.test.ts (2 hunks)
  • src/index.ts (1 hunks)
  • src/specs/RnIap.nitro.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/index.ts
🧰 Additional context used
📓 Path-based instructions (5)
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

When declaring API params/results in TS modules, import canonical types from src/types.ts rather than creating ad‑hoc interfaces.

Files:

  • src/specs/RnIap.nitro.ts
  • src/__tests__/index.test.ts
**/*.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
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • src/specs/RnIap.nitro.ts
  • src/__tests__/index.test.ts
{src/**/*.{ts,tsx},example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}}

📄 CodeRabbit inference engine (CLAUDE.md)

{src/**/*.{ts,tsx},example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}}: Prefer using isUserCancelledError() and getUserFriendlyErrorMessage() with normalized ErrorCode when handling purchase errors.
Use Platform.OS checks for platform-specific logic in React Native code.

Files:

  • src/specs/RnIap.nitro.ts
  • src/__tests__/index.test.ts
{ios/**/*.swift,android/src/main/java/**/*.kt}

📄 CodeRabbit inference engine (CLAUDE.md)

Follow the native class function ordering: (1) properties/init, (2) public cross-platform methods, (3) platform-specific public methods (IOS/Android suffix), (4) event listener methods, (5) private helpers.

Files:

  • ios/HybridRnIap.swift
🧠 Learnings (6)
📓 Common learnings
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 3002
File: docs/docs/getting-started/setup-ios.md:55-60
Timestamp: 2025-09-14T00:13:04.055Z
Learning: The validateReceipt function from useIAP hook in react-native-iap expects a transaction identifier as parameter, which is accessed via purchase.id (not purchase.productId). This is confirmed by the maintainer hyochan and aligns with the library's migration from purchase.transactionId to purchase.id.
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 3015
File: src/types.ts:137-166
Timestamp: 2025-09-18T16:45:10.582Z
Learning: The types in src/types.ts are auto-generated from openiap-gql1.0.6. Breaking changes in mutation return types (like acknowledgePurchaseAndroid returning Promise<boolean>, finishTransaction returning Promise<void>, etc.) originate from updates to this external GraphQL schema generator, not manual refactoring.
Learnt from: CR
Repo: hyochan/react-native-iap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-02T19:35:19.667Z
Learning: Applies to {example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}} : In useIAP hook usage, do not expect returned data from methods that return Promise<void>; consume state from the hook instead.
📚 Learning: 2025-09-18T16:45:10.582Z
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 3015
File: src/types.ts:137-166
Timestamp: 2025-09-18T16:45:10.582Z
Learning: The types in src/types.ts are auto-generated from openiap-gql1.0.6. Breaking changes in mutation return types (like acknowledgePurchaseAndroid returning Promise<boolean>, finishTransaction returning Promise<void>, etc.) originate from updates to this external GraphQL schema generator, not manual refactoring.

Applied to files:

  • src/specs/RnIap.nitro.ts
  • src/__tests__/index.test.ts
📚 Learning: 2025-09-14T00:13:04.055Z
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 3002
File: docs/docs/getting-started/setup-ios.md:55-60
Timestamp: 2025-09-14T00:13:04.055Z
Learning: The validateReceipt function from useIAP hook in react-native-iap expects a transaction identifier as parameter, which is accessed via purchase.id (not purchase.productId). This is confirmed by the maintainer hyochan and aligns with the library's migration from purchase.transactionId to purchase.id.

Applied to files:

  • src/specs/RnIap.nitro.ts
  • src/__tests__/index.test.ts
📚 Learning: 2025-09-13T01:07:18.841Z
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 2999
File: android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt:644-660
Timestamp: 2025-09-13T01:07:18.841Z
Learning: In Android IAP error handling: purchaseToken and productId are distinct properties - purchaseToken identifies a completed purchase transaction (should be null in error cases), while productId is the product SKU for context

Applied to files:

  • src/specs/RnIap.nitro.ts
📚 Learning: 2025-10-02T19:35:19.667Z
Learnt from: CR
Repo: hyochan/react-native-iap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-02T19:35:19.667Z
Learning: Applies to {example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}} : In useIAP hook usage, do not expect returned data from methods that return Promise<void>; consume state from the hook instead.

Applied to files:

  • src/__tests__/index.test.ts
📚 Learning: 2025-10-02T19:35:19.667Z
Learnt from: CR
Repo: hyochan/react-native-iap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-02T19:35:19.667Z
Learning: Applies to {src/**/*.{ts,tsx},example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}} : Prefer using isUserCancelledError() and getUserFriendlyErrorMessage() with normalized ErrorCode when handling purchase errors.

Applied to files:

  • src/__tests__/index.test.ts
  • ios/HybridRnIap.swift
🧬 Code graph analysis (2)
src/__tests__/index.test.ts (2)
android/src/main/java/com/margelo/nitro/iap/RnIapLog.kt (1)
  • result (14-16)
ios/RnIapLog.swift (2)
  • result (41-43)
  • error (35-35)
ios/HybridRnIap.swift (5)
src/types.ts (1)
  • PurchaseError (681-685)
src/utils/errorMapping.ts (1)
  • PurchaseError (51-57)
android/src/main/java/com/margelo/nitro/iap/RnIapLog.kt (3)
  • payload (10-12)
  • result (14-16)
  • failure (18-20)
ios/RnIapLog.swift (4)
  • payload (37-39)
  • result (41-43)
  • error (35-35)
  • failure (45-47)
src/specs/RnIap.nitro.ts (1)
  • NitroAdvancedCommercePurchaseResult (233-242)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-ios
🔇 Additional comments (6)
src/specs/RnIap.nitro.ts (2)

229-242: LGTM! Clean interface definition.

The NitroAdvancedCommercePurchaseResult interface is well-structured with clear field types and comprehensive documentation. The field naming is consistent with existing patterns in the codebase.


710-740: No changes needed to iOS version requirement.

The documented iOS 15.0+ requirement is correct per Apple's official Advanced Commerce API specification. While iOS 18.4 introduced enhancements to StoreKit 2, it does not increase the minimum iOS version for using Product.PurchaseOption.custom with advanced commerce data.

ios/HybridRnIap.swift (2)

804-826: LGTM! Comprehensive method documentation.

The documentation clearly explains the purpose, parameters, return type, and error conditions. The formatting and structure align well with existing methods in the codebase.

Note: The iOS version requirement mentioned here should match what's verified in the Nitro spec review.


827-905: LGTM! Well-implemented purchase flow with proper error handling.

The implementation correctly:

  • Uses JSONSerialization for payload construction (addressing previous review feedback)
  • Respects the andDangerouslyFinishTransactionAutomatically flag (addressing previous review feedback)
  • Handles all purchase result cases including verification failures
  • Creates proper PurchaseError objects for all error paths (addressing previous review feedback)
  • Logs payloads and results for debugging

The code follows existing patterns from the standard requestPurchase method and properly integrates with the StoreKit 2 API.

Note: Ensure the iOS version guard (line 829) aligns with the verification results from the Nitro spec review.

src/__tests__/index.test.ts (2)

39-44: LGTM! Mock properly simulates the native method.

The mock implementation returns a structure that matches NitroAdvancedCommercePurchaseResult with all required fields, enabling comprehensive testing of the public API wrapper.


686-790: LGTM! Comprehensive test coverage for the new API.

The test suite thoroughly validates:

  • Successful purchase flow with proper result mapping
  • iOS-only enforcement with appropriate error on other platforms
  • Error propagation and handling
  • Parameter passing including the optional andDangerouslyFinishTransactionAutomatically flag (both default and explicit values)
  • Result structure matches the expected interface

The tests follow existing patterns in the file and provide good coverage for the new advanced commerce purchase functionality.


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
Copy Markdown
Contributor

Summary of Changes

Hello @hlus, 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 introduces a new iOS-specific purchase method, requestPurchaseWithAdvancedCommerce(), designed to support advanced commerce data within StoreKit 2. This allows applications to pass custom information, such as campaign tokens or affiliate IDs, directly into the purchase process, enhancing analytics and attribution for in-app purchases on iOS 15 and newer.

Highlights

  • New iOS Purchase Method: Introduced requestPurchaseWithAdvancedCommerce() for iOS, enabling advanced commerce data to be passed during StoreKit 2 purchase flows.
  • Custom Data Support: This method allows passing custom data, such as campaign tokens or affiliate IDs, using StoreKit 2's Product.PurchaseOption.custom.
  • Promise-Based Result: Unlike the existing requestPurchase() method, this new function returns the purchase result directly via a Promise, simplifying asynchronous handling.
  • iOS 15+ Requirement: The feature is exclusively available for iOS 15.0 and later due to its reliance on StoreKit 2.
  • API and Documentation: Includes a TypeScript API wrapper, native Swift implementation, and updated documentation for easy integration and understanding.
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.

@codecov

codecov Bot commented Dec 16, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 64.89%. Comparing base (9721c21) to head (1a54a1b).
⚠️ Report is 12 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3106      +/-   ##
==========================================
+ Coverage   64.66%   64.89%   +0.23%     
==========================================
  Files           9        9              
  Lines        1664     1675      +11     
  Branches      559      561       +2     
==========================================
+ Hits         1076     1087      +11     
  Misses        582      582              
  Partials        6        6              
Flag Coverage Δ
library 64.89% <100.00%> (+0.23%) ⬆️

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

Files with missing lines Coverage Δ
src/index.ts 68.85% <100.00%> (+0.42%) ⬆️
🚀 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.

@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 requestPurchaseWithAdvancedCommerce on iOS. The implementation looks good overall, with new native code in Swift, a TypeScript wrapper, and updated documentation.

I have a couple of suggestions:

  • In the Swift implementation, it's better to use JSONSerialization instead of string interpolation to build the JSON payload for robustness.
  • In the TypeScript wrapper, defining an explicit type for the purchase result would improve type safety and maintainability.

The rest of the changes, including documentation and spec updates, are well done.

Comment thread ios/HybridRnIap.swift Outdated
Comment thread src/index.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
docs/docs/api/methods/core-methods.md (1)

887-895: Add blank lines around the JSON code block for markdown consistency.

The JSON code block needs blank lines before and after it to satisfy markdown linting rules (MD031).

 **Note:** The advanced commerce data is passed to StoreKit as JSON:
+
 ```json
 {
   "signatureInfo": {
     "token": "<advancedCommerceData>"
   }
 }

</blockquote></details>
<details>
<summary>src/index.ts (1)</summary><blockquote>

`1721-1758`: **Implementation is solid with proper platform check and error handling.**

The implementation correctly:
- Uses `Platform.OS` check for iOS-only functionality
- Delegates to the native iOS method
- Maps errors to `PurchaseError` with the `productId` included
- Follows existing error handling patterns in the file

One minor consideration: the return type is defined inline, but `NitroAdvancedCommercePurchaseResult` is already defined in the Nitro specs. You could use a type-only import for consistency with coding guidelines, though the inline type is functionally equivalent.

```diff
+import type {
+  NitroAdvancedCommercePurchaseResult,
+  // ... existing imports
+} from './specs/RnIap.nitro';

-export const requestPurchaseWithAdvancedCommerce = async (
-  productId: string,
-  advancedCommerceData: string,
-): Promise<{
-  success: boolean;
-  transactionId: string;
-  productId: string;
-  purchaseDate: number;
-}> => {
+export const requestPurchaseWithAdvancedCommerce = async (
+  productId: string,
+  advancedCommerceData: string,
+): Promise<NitroAdvancedCommercePurchaseResult> => {
ios/HybridRnIap.swift (1)

806-808: Redundant iOS 15 availability check.

The class is already marked with @available(iOS 15.0, *) at line 6, so this guard will never be triggered at runtime. The check is defensive but unnecessary.

-            guard #available(iOS 15.0, *) else {
-                throw PurchaseError.make(code: .featureNotSupported, message: "StoreKit 2 requires iOS 15.0 or later")
-            }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5bcc934 and de2591c.

⛔ Files ignored due to path filters (1)
  • example/ios/Podfile.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • docs/docs/api/methods/core-methods.md (1 hunks)
  • ios/HybridRnIap.swift (2 hunks)
  • src/index.ts (1 hunks)
  • src/specs/RnIap.nitro.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

When declaring API params/results in TS modules, import canonical types from src/types.ts rather than creating ad‑hoc interfaces.

Files:

  • src/specs/RnIap.nitro.ts
  • src/index.ts
**/*.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
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • src/specs/RnIap.nitro.ts
  • src/index.ts
{src/**/*.{ts,tsx},example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}}

📄 CodeRabbit inference engine (CLAUDE.md)

{src/**/*.{ts,tsx},example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}}: Prefer using isUserCancelledError() and getUserFriendlyErrorMessage() with normalized ErrorCode when handling purchase errors.
Use Platform.OS checks for platform-specific logic in React Native code.

Files:

  • src/specs/RnIap.nitro.ts
  • src/index.ts
{ios/**/*.swift,android/src/main/java/**/*.kt}

📄 CodeRabbit inference engine (CLAUDE.md)

Follow the native class function ordering: (1) properties/init, (2) public cross-platform methods, (3) platform-specific public methods (IOS/Android suffix), (4) event listener methods, (5) private helpers.

Files:

  • ios/HybridRnIap.swift
🧠 Learnings (6)
📓 Common learnings
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 3015
File: src/types.ts:137-166
Timestamp: 2025-09-18T16:45:10.582Z
Learning: The types in src/types.ts are auto-generated from openiap-gql1.0.6. Breaking changes in mutation return types (like acknowledgePurchaseAndroid returning Promise<boolean>, finishTransaction returning Promise<void>, etc.) originate from updates to this external GraphQL schema generator, not manual refactoring.
📚 Learning: 2025-09-18T16:45:10.582Z
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 3015
File: src/types.ts:137-166
Timestamp: 2025-09-18T16:45:10.582Z
Learning: The types in src/types.ts are auto-generated from openiap-gql1.0.6. Breaking changes in mutation return types (like acknowledgePurchaseAndroid returning Promise<boolean>, finishTransaction returning Promise<void>, etc.) originate from updates to this external GraphQL schema generator, not manual refactoring.

Applied to files:

  • src/specs/RnIap.nitro.ts
  • src/index.ts
📚 Learning: 2025-09-14T00:13:04.055Z
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 3002
File: docs/docs/getting-started/setup-ios.md:55-60
Timestamp: 2025-09-14T00:13:04.055Z
Learning: The validateReceipt function from useIAP hook in react-native-iap expects a transaction identifier as parameter, which is accessed via purchase.id (not purchase.productId). This is confirmed by the maintainer hyochan and aligns with the library's migration from purchase.transactionId to purchase.id.

Applied to files:

  • src/specs/RnIap.nitro.ts
  • src/index.ts
  • docs/docs/api/methods/core-methods.md
📚 Learning: 2025-09-13T01:07:18.841Z
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 2999
File: android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt:644-660
Timestamp: 2025-09-13T01:07:18.841Z
Learning: In Android IAP error handling: purchaseToken and productId are distinct properties - purchaseToken identifies a completed purchase transaction (should be null in error cases), while productId is the product SKU for context

Applied to files:

  • src/specs/RnIap.nitro.ts
  • src/index.ts
📚 Learning: 2025-10-02T19:35:19.667Z
Learnt from: CR
Repo: hyochan/react-native-iap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-02T19:35:19.667Z
Learning: Applies to {src/**/*.{ts,tsx},example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}} : Prefer using isUserCancelledError() and getUserFriendlyErrorMessage() with normalized ErrorCode when handling purchase errors.

Applied to files:

  • src/index.ts
📚 Learning: 2025-10-02T19:35:19.667Z
Learnt from: CR
Repo: hyochan/react-native-iap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-02T19:35:19.667Z
Learning: Applies to {example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}} : In useIAP hook usage, do not expect returned data from methods that return Promise<void>; consume state from the hook instead.

Applied to files:

  • src/index.ts
🧬 Code graph analysis (1)
ios/HybridRnIap.swift (4)
src/types.ts (1)
  • PurchaseError (681-685)
src/utils/errorMapping.ts (1)
  • PurchaseError (51-57)
ios/RnIapLog.swift (4)
  • payload (37-39)
  • result (41-43)
  • error (35-35)
  • failure (45-47)
src/specs/RnIap.nitro.ts (1)
  • NitroAdvancedCommercePurchaseResult (232-237)
🪛 markdownlint-cli2 (0.18.1)
docs/docs/api/methods/core-methods.md

888-888: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-ios
🔇 Additional comments (5)
docs/docs/api/methods/core-methods.md (1)

856-886: Documentation looks good and aligns with the implementation.

The documentation correctly describes the new API, including:

  • iOS 15+ requirement
  • Promise-based return (unlike event-based requestPurchase())
  • Error handling pattern with ErrorCode.UserCancelled
  • Parameter descriptions and return type
src/specs/RnIap.nitro.ts (2)

229-237: Type definition is well-structured.

The NitroAdvancedCommercePurchaseResult interface is clean, minimal, and provides all necessary fields for the purchase result.


705-716: Method signature follows established patterns.

The method signature correctly:

  • Uses the IOS suffix per coding guidelines for platform-specific methods
  • Documents the iOS 15+ requirement
  • References the new result type
ios/HybridRnIap.swift (2)

856-858: Auto-finishing the transaction immediately may not align with user expectations.

Unlike the standard requestPurchase() flow which allows developers to validate the purchase server-side before calling finishTransaction(), this method auto-finishes immediately. This could be problematic if:

  1. Server-side validation fails after the transaction is finished
  2. The app crashes before the purchase is recorded

Consider either:

  • Documenting this behavior explicitly in the API docs
  • Adding an optional parameter to control auto-finish behavior
  • Storing the transaction payload like other methods do (via purchasePayloadById) to allow manual finish

Is the auto-finish behavior intentional for this advanced commerce flow? If so, please document this difference from the standard purchase flow.


804-882: Overall implementation follows established patterns.

The method correctly:

  • Ensures connection before proceeding
  • Uses RnIapLog for consistent logging
  • Handles all StoreKit 2 purchase result cases (success/verified, unverified, userCancelled, pending, unknown)
  • Maps errors to appropriate PurchaseError codes
  • Re-throws PurchaseError instances without wrapping

The direct use of StoreKit 2 API (instead of OpenIAP) is appropriate here since Product.PurchaseOption.custom is not exposed through OpenIAP.

Comment thread ios/HybridRnIap.swift Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
ios/HybridRnIap.swift (1)

844-851: Use JSONSerialization to prevent JSON injection.

String interpolation can produce invalid JSON if advancedCommerceData contains quotes, backslashes, or newlines.

Apply this diff to use proper JSON encoding:

-                let request = """
-                {
-                   "signatureInfo": {
-                      "token": "\(advancedCommerceData)"
-                   }
-                }
-                """
-                let advancedCommerceRequestData = Data(request.utf8)
+                let payload: [String: Any] = [
+                    "signatureInfo": [
+                        "token": advancedCommerceData
+                    ]
+                ]
+                let advancedCommerceRequestData = try JSONSerialization.data(withJSONObject: payload)
🧹 Nitpick comments (2)
src/__tests__/index.test.ts (2)

718-730: Consider verifying actual error code normalization.

The test mentions "normalizes error codes" but only checks that an error is thrown. Consider verifying the error properties if normalization is expected:

-        await expect(
-          IAP.requestPurchaseWithAdvancedCommerce('product-1', 'token'),
-        ).rejects.toThrow();
+        await expect(
+          IAP.requestPurchaseWithAdvancedCommerce('product-1', 'token'),
+        ).rejects.toMatchObject({
+          message: expect.any(String),
+          productId: 'product-1', // if normalization adds productId
+        });

732-747: Consider more idiomatic error assertion pattern.

The try/catch with fail() works but Jest's async matchers are more idiomatic:

-        try {
-          await IAP.requestPurchaseWithAdvancedCommerce('product-1', 'token');
-          fail('Should have thrown');
-        } catch (error: any) {
-          expect(error).toBeDefined();
-        }
+        await expect(
+          IAP.requestPurchaseWithAdvancedCommerce('product-1', 'token'),
+        ).rejects.toThrow();

Or to verify specific properties:

await expect(
  IAP.requestPurchaseWithAdvancedCommerce('product-1', 'token'),
).rejects.toMatchObject({
  message: 'Purchase failed',
});
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between de2591c and 16cd3f8.

📒 Files selected for processing (4)
  • ios/HybridRnIap.swift (2 hunks)
  • src/__tests__/index.test.ts (2 hunks)
  • src/index.ts (1 hunks)
  • src/specs/RnIap.nitro.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/index.ts
  • src/specs/RnIap.nitro.ts
🧰 Additional context used
📓 Path-based instructions (4)
{ios/**/*.swift,android/src/main/java/**/*.kt}

📄 CodeRabbit inference engine (CLAUDE.md)

Follow the native class function ordering: (1) properties/init, (2) public cross-platform methods, (3) platform-specific public methods (IOS/Android suffix), (4) event listener methods, (5) private helpers.

Files:

  • ios/HybridRnIap.swift
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

When declaring API params/results in TS modules, import canonical types from src/types.ts rather than creating ad‑hoc interfaces.

Files:

  • src/__tests__/index.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • src/__tests__/index.test.ts
{src/**/*.{ts,tsx},example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}}

📄 CodeRabbit inference engine (CLAUDE.md)

{src/**/*.{ts,tsx},example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}}: Prefer using isUserCancelledError() and getUserFriendlyErrorMessage() with normalized ErrorCode when handling purchase errors.
Use Platform.OS checks for platform-specific logic in React Native code.

Files:

  • src/__tests__/index.test.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 3015
File: src/types.ts:137-166
Timestamp: 2025-09-18T16:45:10.582Z
Learning: The types in src/types.ts are auto-generated from openiap-gql1.0.6. Breaking changes in mutation return types (like acknowledgePurchaseAndroid returning Promise<boolean>, finishTransaction returning Promise<void>, etc.) originate from updates to this external GraphQL schema generator, not manual refactoring.
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 3002
File: docs/docs/getting-started/setup-ios.md:55-60
Timestamp: 2025-09-14T00:13:04.055Z
Learning: The validateReceipt function from useIAP hook in react-native-iap expects a transaction identifier as parameter, which is accessed via purchase.id (not purchase.productId). This is confirmed by the maintainer hyochan and aligns with the library's migration from purchase.transactionId to purchase.id.
Learnt from: CR
Repo: hyochan/react-native-iap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-02T19:35:19.667Z
Learning: Applies to {example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}} : In useIAP hook usage, do not expect returned data from methods that return Promise<void>; consume state from the hook instead.
📚 Learning: 2025-09-18T16:45:10.582Z
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 3015
File: src/types.ts:137-166
Timestamp: 2025-09-18T16:45:10.582Z
Learning: The types in src/types.ts are auto-generated from openiap-gql1.0.6. Breaking changes in mutation return types (like acknowledgePurchaseAndroid returning Promise<boolean>, finishTransaction returning Promise<void>, etc.) originate from updates to this external GraphQL schema generator, not manual refactoring.

Applied to files:

  • src/__tests__/index.test.ts
📚 Learning: 2025-10-02T19:35:19.667Z
Learnt from: CR
Repo: hyochan/react-native-iap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-02T19:35:19.667Z
Learning: Applies to {example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}} : In useIAP hook usage, do not expect returned data from methods that return Promise<void>; consume state from the hook instead.

Applied to files:

  • src/__tests__/index.test.ts
📚 Learning: 2025-09-14T00:13:04.055Z
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 3002
File: docs/docs/getting-started/setup-ios.md:55-60
Timestamp: 2025-09-14T00:13:04.055Z
Learning: The validateReceipt function from useIAP hook in react-native-iap expects a transaction identifier as parameter, which is accessed via purchase.id (not purchase.productId). This is confirmed by the maintainer hyochan and aligns with the library's migration from purchase.transactionId to purchase.id.

Applied to files:

  • src/__tests__/index.test.ts
📚 Learning: 2025-10-02T19:35:19.667Z
Learnt from: CR
Repo: hyochan/react-native-iap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-02T19:35:19.667Z
Learning: Applies to {src/**/*.{ts,tsx},example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}} : Prefer using isUserCancelledError() and getUserFriendlyErrorMessage() with normalized ErrorCode when handling purchase errors.

Applied to files:

  • src/__tests__/index.test.ts
🧬 Code graph analysis (2)
ios/HybridRnIap.swift (5)
src/types.ts (1)
  • PurchaseError (681-685)
src/utils/errorMapping.ts (1)
  • PurchaseError (51-57)
android/src/main/java/com/margelo/nitro/iap/RnIapLog.kt (3)
  • payload (10-12)
  • result (14-16)
  • failure (18-20)
ios/RnIapLog.swift (4)
  • payload (37-39)
  • result (41-43)
  • error (35-35)
  • failure (45-47)
src/specs/RnIap.nitro.ts (1)
  • NitroAdvancedCommercePurchaseResult (233-242)
src/__tests__/index.test.ts (2)
android/src/main/java/com/margelo/nitro/iap/RnIapLog.kt (1)
  • result (14-16)
ios/RnIapLog.swift (2)
  • result (41-43)
  • error (35-35)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-ios
🔇 Additional comments (4)
ios/HybridRnIap.swift (2)

4-4: LGTM: StoreKit import added for new API.

The StoreKit import is necessary for the Product.PurchaseOption.custom API used in the new advanced commerce method.


877-879: Verify automatic transaction finishing aligns with library design.

This method automatically finishes the transaction, which differs from the standard requestPurchase() flow where developers manually call finishTransaction(). While this may be intentional for a synchronous result API, it removes developer control over the transaction lifecycle (e.g., verifying server-side receipt validation before finishing).

Confirm this is the intended behavior. If server-side validation is needed before finishing, consider:

  • Returning the transaction without finishing
  • Adding a parameter to control auto-finish behavior
  • Documenting that transactions are auto-finished
src/__tests__/index.test.ts (2)

39-44: LGTM: Mock properly structured.

The mock for requestPurchaseWithAdvancedCommerceIOS returns the correct shape matching NitroAdvancedCommercePurchaseResult.


686-769: LGTM: Comprehensive test coverage.

The test suite covers the essential scenarios: successful purchase, platform validation, error propagation, and property mapping. Good coverage of both happy and error paths.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
ios/HybridRnIap.swift (1)

844-851: JSON injection vulnerability already flagged.

This issue has been identified in previous reviews. String interpolation without proper escaping can produce invalid JSON or allow injection if advancedCommerceData contains quotes, backslashes, or newlines. Use JSONSerialization instead.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16cd3f8 and 78179e6.

📒 Files selected for processing (2)
  • ios/HybridRnIap.swift (2 hunks)
  • src/index.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

When declaring API params/results in TS modules, import canonical types from src/types.ts rather than creating ad‑hoc interfaces.

Files:

  • src/index.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • src/index.ts
{src/**/*.{ts,tsx},example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}}

📄 CodeRabbit inference engine (CLAUDE.md)

{src/**/*.{ts,tsx},example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}}: Prefer using isUserCancelledError() and getUserFriendlyErrorMessage() with normalized ErrorCode when handling purchase errors.
Use Platform.OS checks for platform-specific logic in React Native code.

Files:

  • src/index.ts
{ios/**/*.swift,android/src/main/java/**/*.kt}

📄 CodeRabbit inference engine (CLAUDE.md)

Follow the native class function ordering: (1) properties/init, (2) public cross-platform methods, (3) platform-specific public methods (IOS/Android suffix), (4) event listener methods, (5) private helpers.

Files:

  • ios/HybridRnIap.swift
🧠 Learnings (6)
📓 Common learnings
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 3002
File: docs/docs/getting-started/setup-ios.md:55-60
Timestamp: 2025-09-14T00:13:04.055Z
Learning: The validateReceipt function from useIAP hook in react-native-iap expects a transaction identifier as parameter, which is accessed via purchase.id (not purchase.productId). This is confirmed by the maintainer hyochan and aligns with the library's migration from purchase.transactionId to purchase.id.
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 3015
File: src/types.ts:137-166
Timestamp: 2025-09-18T16:45:10.582Z
Learning: The types in src/types.ts are auto-generated from openiap-gql1.0.6. Breaking changes in mutation return types (like acknowledgePurchaseAndroid returning Promise<boolean>, finishTransaction returning Promise<void>, etc.) originate from updates to this external GraphQL schema generator, not manual refactoring.
Learnt from: CR
Repo: hyochan/react-native-iap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-02T19:35:19.667Z
Learning: Applies to {example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}} : In useIAP hook usage, do not expect returned data from methods that return Promise<void>; consume state from the hook instead.
📚 Learning: 2025-09-18T16:45:10.582Z
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 3015
File: src/types.ts:137-166
Timestamp: 2025-09-18T16:45:10.582Z
Learning: The types in src/types.ts are auto-generated from openiap-gql1.0.6. Breaking changes in mutation return types (like acknowledgePurchaseAndroid returning Promise<boolean>, finishTransaction returning Promise<void>, etc.) originate from updates to this external GraphQL schema generator, not manual refactoring.

Applied to files:

  • src/index.ts
📚 Learning: 2025-10-02T19:35:19.667Z
Learnt from: CR
Repo: hyochan/react-native-iap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-02T19:35:19.667Z
Learning: Applies to {src/**/*.{ts,tsx},example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}} : Prefer using isUserCancelledError() and getUserFriendlyErrorMessage() with normalized ErrorCode when handling purchase errors.

Applied to files:

  • src/index.ts
📚 Learning: 2025-09-14T00:13:04.055Z
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 3002
File: docs/docs/getting-started/setup-ios.md:55-60
Timestamp: 2025-09-14T00:13:04.055Z
Learning: The validateReceipt function from useIAP hook in react-native-iap expects a transaction identifier as parameter, which is accessed via purchase.id (not purchase.productId). This is confirmed by the maintainer hyochan and aligns with the library's migration from purchase.transactionId to purchase.id.

Applied to files:

  • src/index.ts
📚 Learning: 2025-10-02T19:35:19.667Z
Learnt from: CR
Repo: hyochan/react-native-iap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-02T19:35:19.667Z
Learning: Applies to {example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}} : In useIAP hook usage, do not expect returned data from methods that return Promise<void>; consume state from the hook instead.

Applied to files:

  • src/index.ts
📚 Learning: 2025-09-13T01:07:18.841Z
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 2999
File: android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt:644-660
Timestamp: 2025-09-13T01:07:18.841Z
Learning: In Android IAP error handling: purchaseToken and productId are distinct properties - purchaseToken identifies a completed purchase transaction (should be null in error cases), while productId is the product SKU for context

Applied to files:

  • src/index.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-ios
🔇 Additional comments (2)
src/index.ts (2)

1760-1792: LGTM! Implementation follows established patterns.

The function implementation correctly:

  • Guards for iOS platform (line 1764)
  • Handles errors with proper parsing and mapping
  • Passes productId to error context for debugging
  • Maps the Nitro result to the public interface

1697-1709: Move interface to src/types.ts per coding guidelines.

As per coding guidelines for src/**/*.{ts,tsx}: "When declaring API params/results in TS modules, import canonical types from src/types.ts rather than creating ad-hoc interfaces." Define AdvancedCommercePurchaseResult in src/types.ts and import it here using a type-only import.

Based on coding guidelines, move the interface to src/types.ts:

// In src/types.ts
export interface AdvancedCommercePurchaseResult {
  /** Whether the purchase completed successfully */
  success: boolean;
  /** Unique transaction identifier from StoreKit */
  transactionId: string;
  /** Product identifier that was purchased */
  productId: string;
  /** Purchase timestamp in milliseconds since Unix epoch */
  purchaseDate: number;
}

Then import in src/index.ts:

+import type {
+  // ... existing imports
+  AdvancedCommercePurchaseResult,
+} from './types';

-/**
- * Result of a purchase with advanced commerce data
- */
-export interface AdvancedCommercePurchaseResult {
-  /** Whether the purchase completed successfully */
-  success: boolean;
-  /** Unique transaction identifier from StoreKit */
-  transactionId: string;
-  /** Product identifier that was purchased */
-  productId: string;
-  /** Purchase timestamp in milliseconds since Unix epoch */
-  purchaseDate: number;
-}
⛔ Skipped due to learnings
Learnt from: CR
Repo: hyochan/react-native-iap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-02T19:35:19.667Z
Learning: Applies to src/**/*.{ts,tsx} : When declaring API params/results in TS modules, import canonical types from src/types.ts rather than creating ad‑hoc interfaces.
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 3015
File: src/types.ts:137-166
Timestamp: 2025-09-18T16:45:10.582Z
Learning: The types in src/types.ts are auto-generated from openiap-gql1.0.6. Breaking changes in mutation return types (like acknowledgePurchaseAndroid returning Promise<boolean>, finishTransaction returning Promise<void>, etc.) originate from updates to this external GraphQL schema generator, not manual refactoring.
Learnt from: CR
Repo: hyochan/react-native-iap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-02T19:35:19.667Z
Learning: Applies to src/types.ts : Never edit src/types.ts manually; it is generated. Import canonical types from this file instead of defining ad‑hoc interfaces.
Learnt from: CR
Repo: hyochan/react-native-iap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-02T19:35:19.667Z
Learning: Applies to {src/**/*.{ts,tsx},example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}} : Prefer using isUserCancelledError() and getUserFriendlyErrorMessage() with normalized ErrorCode when handling purchase errors.

Comment thread ios/HybridRnIap.swift Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
ios/HybridRnIap.swift (2)

844-851: Use proper JSON serialization to avoid injection issues.

String interpolation can produce invalid JSON if advancedCommerceData contains quotes, backslashes, or newlines.

Apply this diff to use safe JSON serialization:

-                let request = """
-                {
-                   "signatureInfo": {
-                      "token": "\(advancedCommerceData)"
-                   }
-                }
-                """
-                let advancedCommerceRequestData = Data(request.utf8)
+                let payload: [String: Any] = [
+                    "signatureInfo": [
+                        "token": advancedCommerceData
+                    ]
+                ]
+                let advancedCommerceRequestData = try JSONSerialization.data(withJSONObject: payload)

877-879: Consider adding control over transaction finishing.

Unlike the standard requestPurchase flow, this auto-finishes without user control, which may prevent server-side verification before acknowledgment.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 78179e6 and 99aba66.

📒 Files selected for processing (1)
  • ios/HybridRnIap.swift (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
{ios/**/*.swift,android/src/main/java/**/*.kt}

📄 CodeRabbit inference engine (CLAUDE.md)

Follow the native class function ordering: (1) properties/init, (2) public cross-platform methods, (3) platform-specific public methods (IOS/Android suffix), (4) event listener methods, (5) private helpers.

Files:

  • ios/HybridRnIap.swift
🧠 Learnings (1)
📓 Common learnings
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 3002
File: docs/docs/getting-started/setup-ios.md:55-60
Timestamp: 2025-09-14T00:13:04.055Z
Learning: The validateReceipt function from useIAP hook in react-native-iap expects a transaction identifier as parameter, which is accessed via purchase.id (not purchase.productId). This is confirmed by the maintainer hyochan and aligns with the library's migration from purchase.transactionId to purchase.id.
Learnt from: hyochan
Repo: hyochan/react-native-iap PR: 3015
File: src/types.ts:137-166
Timestamp: 2025-09-18T16:45:10.582Z
Learning: The types in src/types.ts are auto-generated from openiap-gql1.0.6. Breaking changes in mutation return types (like acknowledgePurchaseAndroid returning Promise<boolean>, finishTransaction returning Promise<void>, etc.) originate from updates to this external GraphQL schema generator, not manual refactoring.
Learnt from: CR
Repo: hyochan/react-native-iap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-02T19:35:19.667Z
Learning: Applies to {example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}} : In useIAP hook usage, do not expect returned data from methods that return Promise<void>; consume state from the hook instead.
🧬 Code graph analysis (1)
ios/HybridRnIap.swift (3)
src/utils/errorMapping.ts (1)
  • PurchaseError (51-57)
ios/RnIapLog.swift (4)
  • payload (37-39)
  • result (41-43)
  • error (35-35)
  • failure (45-47)
src/specs/RnIap.nitro.ts (1)
  • NitroAdvancedCommercePurchaseResult (233-242)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-ios

Comment thread ios/HybridRnIap.swift Outdated
Comment thread ios/HybridRnIap.swift Outdated
@hlus hlus marked this pull request as draft December 18, 2025 16:09
@hlus

hlus commented Dec 18, 2025

Copy link
Copy Markdown
Author

Want to retest this with advanced commerce API connecte to confirm if this approach is works

@hyochan

hyochan commented Dec 23, 2025

Copy link
Copy Markdown
Owner

Thank you for this contribution, @hlus! 🙏

We maintain native IAP implementations in OpenIAP, a shared Swift/Kotlin library that both react-native-iap and expo-iap consume. So we'll implement this feature there first.

Implementation Plan

We'll integrate this feature into the existing requestPurchase API rather than creating a separate method:

input RequestPurchaseIosProps {
  sku: String!
  andDangerouslyFinishTransactionAutomatically: Boolean
  appAccountToken: String
  quantity: Int
  withOffer: DiscountOfferInputIOS
  # New field
  advancedCommerceDataIOS: String  # Campaign tokens, affiliate IDs, etc.
}

Usage will look like:

requestPurchase({
  request: {
    apple: {
      sku: 'com.example.premium',
      advancedCommerceDataIOS: 'campaign_token_12345',
    }
  },
  type: 'in-app'
});

// Results come through the existing PurchaseUpdated event

This keeps API consistency and backward compatibility. Results come through the existing PurchaseUpdated event. I'll add you as a co-author when implementing this in react-native-iap. Thanks again!

I'll be starting this implementation soon and will add you as a co-author on the commits. Thanks again for bringing this feature to our attention!

@hyochan hyochan added 🏃🏻‍♀️ in progress Currently working on 🥺 feature request Request a new feature 📱 iOS Related to iOS and removed 🥺 feature request Request a new feature labels Dec 23, 2025
@hyochan

hyochan commented Dec 23, 2025

Copy link
Copy Markdown
Owner

Will be handled in #3112

@hyochan hyochan left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Could you kindly check feature in #3112 by installing react-native-iap@next? It will download version 14.6.3-rc.1.

You can use as same as expo-iap https://hyochan.github.io/expo-iap/api/methods/unified-apis?_highlight=requestpurchase#ios-with-advanced-commerce-data-ios-15

hyochan added a commit that referenced this pull request Dec 24, 2025
- Add advancedCommerceDataIOS support for StoreKit 2 attribution
tracking
- Support google/apple fields with android/ios fallback (deprecated)
- Deprecate requestPurchaseOnPromotedProductIOS
- Update documentation with new field names
- Add tests for new features
- Update OpenIAP to `apple 1.3.6`, `google 1.3.15`, `gql 1.3.6`

Closes #3106

Co-authored-by: hlus <kyshbogdan@gmail.com>

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added iOS advancedCommerceData support and new Android billing-program
& external-link purchase helpers; promoted-product flow now returns a
boolean and supports a StoreKit 2 recommended listener + requestPurchase
path.
  * Examples now include type: 'in-app' in purchase calls.

* **Documentation**
* Updated examples and guides to use apple/google keys (ios/android
deprecated), subscriptionOffers guidance, and promoted-product
deprecation notes.

* **Tests**
* Added tests for apple/google precedence and iOS advancedCommerceData
propagation.

* **Chores**
  * Bumped platform/docs version metadata.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@hyochan

hyochan commented Dec 24, 2025

Copy link
Copy Markdown
Owner

Just merged it! Hope this works for you

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

Labels

🥺 feature request Request a new feature 🏃🏻‍♀️ in progress Currently working on 📱 iOS Related to iOS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants