feat(ios): add requestPurchaseWithAdvancedCommerce support#3106
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (1)
🧰 Additional context used📓 Path-based instructions (5)src/**/*.{ts,tsx}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
**/*.nitro.ts📄 CodeRabbit inference engine (CLAUDE.md)
Files:
**/*.{ts,tsx}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
{src/**/*.{ts,tsx},example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
{ios/**/*.swift,android/src/main/java/**/*.kt}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
🧠 Learnings (6)📓 Common learnings📚 Learning: 2025-09-18T16:45:10.582ZApplied to files:
📚 Learning: 2025-09-14T00:13:04.055ZApplied to files:
📚 Learning: 2025-09-13T01:07:18.841ZApplied to files:
📚 Learning: 2025-10-02T19:35:19.667ZApplied to files:
📚 Learning: 2025-10-02T19:35:19.667ZApplied to files:
🧬 Code graph analysis (2)src/__tests__/index.test.ts (2)
ios/HybridRnIap.swift (5)
⏰ 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)
🔇 Additional comments (6)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @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, Highlights
Using Gemini Code AssistThe 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
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 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
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ 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
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
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
JSONSerializationinstead 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.
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
example/ios/Podfile.lockis 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.tssrc/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.tssrc/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.tssrc/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.tssrc/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.tssrc/index.tsdocs/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.tssrc/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
NitroAdvancedCommercePurchaseResultinterface 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
IOSsuffix 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 callingfinishTransaction(), this method auto-finishes immediately. This could be problematic if:
- Server-side validation fails after the transaction is finished
- 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 finishIs 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.customis not exposed through OpenIAP.
There was a problem hiding this comment.
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
advancedCommerceDatacontains 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
📒 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.customAPI 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 callfinishTransaction(). 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
requestPurchaseWithAdvancedCommerceIOSreturns the correct shape matchingNitroAdvancedCommercePurchaseResult.
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.
There was a problem hiding this comment.
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
advancedCommerceDatacontains quotes, backslashes, or newlines. UseJSONSerializationinstead.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 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
productIdto error context for debugging- Maps the Nitro result to the public interface
1697-1709: Move interface tosrc/types.tsper 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." DefineAdvancedCommercePurchaseResultinsrc/types.tsand 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.
There was a problem hiding this comment.
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
advancedCommerceDatacontains 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
requestPurchaseflow, this auto-finishes without user control, which may prevent server-side verification before acknowledgment.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 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
|
Want to retest this with advanced commerce API connecte to confirm if this approach is works |
|
Thank you for this contribution, @hlus! 🙏 We maintain native IAP implementations in OpenIAP, a shared Swift/Kotlin library that both Implementation PlanWe'll integrate this feature into the existing 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
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! |
|
Will be handled in #3112 |
hyochan
left a comment
There was a problem hiding this comment.
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
- 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 -->
|
Just merged it! Hope this works for you |
Description
Adds
requestPurchaseWithAdvancedCommerce()method for iOS that enables passing custom advanced commerce data during StoreKit 2 purchase flows usingProduct.PurchaseOption.custom.Changes
NitroAdvancedCommercePurchaseResulttype to Nitro specrequestPurchaseWithAdvancedCommerceIOSin Swift using StoreKit 2docs/docs/api/methods/core-methods.mdRequirements
Testing
yarn typecheck:lib)yarn lint --fix)yarn specs)Summary by CodeRabbit
New Features
Documentation
Tests
✏️ Tip: You can customize this high-level summary in your review settings.