feat(api): restruct verifyPurchase with platform-specific opts#3104
feat(api): restruct verifyPurchase with platform-specific opts#3104
Conversation
BREAKING CHANGE: verifyPurchase/validateReceipt API now uses platform-specific options - Move `sku` from root level into platform options (apple.sku, google.sku, horizon.sku) - Rename `androidOptions` to `google` - Rename `productToken` to `purchaseToken` in google options - Add `horizon` options for Meta Quest verification - Fix Android validateReceipt to call actual OpenIAP verifyPurchase instead of returning mock data - Update Nitro types for platform-specific validation params - Update all example apps and documentation - Update tests for new API structure
|
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. WalkthroughRefactors receipt verification to platform-specific option objects ( Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant App as App / Example
participant JS as JS Library
participant Native as Native Bridge (iOS / Android)
participant OpenIAP as OpenIAP Provider
App->>JS: validateReceipt({ apple?|google?|horizon? }) / verifyPurchase(...)
JS->>JS: validate & normalize platform props
JS->>Native: call native validate/verify with platform props
alt Android (google)
Native->>OpenIAP: verifyPurchase(VerifyPurchaseGoogleOptions)
OpenIAP-->>Native: VerifyPurchaseResultAndroid
else iOS (apple)
Native->>OpenIAP: validateReceiptIOS(apple.sku)
OpenIAP-->>Native: VerifyPurchaseResultIOS
else Horizon
Native->>OpenIAP: verifyPurchase(VerifyPurchaseHorizonOptions)
OpenIAP-->>Native: VerifyPurchaseResultHorizon
end
Native-->>JS: mapped VerifyPurchaseResult*
JS-->>App: Promise<VerifyPurchaseResult>
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 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)
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 @hyochan, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a significant breaking change to the 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
|
There was a problem hiding this comment.
Code Review
This pull request effectively restructures the verifyPurchase and validateReceipt APIs to be more robust and extensible by using platform-specific options. The changes are well-executed across the TypeScript, Swift, and Kotlin codebases, including updates to documentation, examples, and tests. The introduction of support for Meta Horizon and fixing the Android validation to use the real OpenIAP implementation are significant improvements. My feedback focuses on minor opportunities to improve code maintainability by reducing repetitive validation logic. Overall, this is a solid and well-thought-out breaking change that improves the library's architecture.
Update documentation to reflect new platform-specific options structure: - verifyPurchase() now uses apple, google, horizon options instead of root-level sku/androidOptions - validateReceipt in useIAP hook updated with new API structure - Added VerifyPurchaseProps, VerifyPurchaseAppleOptions, VerifyPurchaseGoogleOptions, VerifyPurchaseHorizonOptions types to types.md - Added verification result types documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code)
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt (1)
1125-1211: Avoid leaking verification payload viaverifyResult.toString()+ improve error mapping.
verifyResult.toString()(Line 1167) is an uncontrolled surface and may include sensitive/internal fields depending on OpenIAP’s implementation.- The generic catch maps all failures to
InvalidPurchaseVerification(Line 1203), which can misclassify network/service errors and regress UX/triage (you already haveparseOpenIapError()for better categorization).@@ - val verifyResult = openIap.verifyPurchase(props) - RnIapLog.result("validateReceipt", verifyResult.toString()) + val verifyResult = openIap.verifyPurchase(props) + // Avoid logging verifyResult.toString() to prevent leaking sensitive/internal fields. + RnIapLog.result( + "validateReceipt", + mapOf("type" to verifyResult::class.java.simpleName) + ) @@ - if (nitroGoogleOptions.sku.isEmpty()) { + if (nitroGoogleOptions.sku.isBlank()) { throw OpenIapException(toErrorJson(OpenIAPError.DeveloperError, debugMessage = "Missing or empty required parameter: google.sku")) } - if (nitroGoogleOptions.accessToken.isEmpty()) { + if (nitroGoogleOptions.accessToken.isBlank()) { throw OpenIapException(toErrorJson(OpenIAPError.DeveloperError, debugMessage = "Missing or empty required parameter: google.accessToken")) } - if (nitroGoogleOptions.packageName.isEmpty()) { + if (nitroGoogleOptions.packageName.isBlank()) { throw OpenIapException(toErrorJson(OpenIAPError.DeveloperError, debugMessage = "Missing or empty required parameter: google.packageName")) } - if (nitroGoogleOptions.purchaseToken.isEmpty()) { + if (nitroGoogleOptions.purchaseToken.isBlank()) { throw OpenIapException(toErrorJson(OpenIAPError.DeveloperError, debugMessage = "Missing or empty required parameter: google.purchaseToken")) } @@ - } catch (e: Exception) { + } catch (e: Exception) { RnIapLog.failure("validateReceipt", e) val debugMessage = e.message - val error = OpenIAPError.InvalidPurchaseVerification + val error = parseOpenIapError(e) throw OpenIapException( toErrorJson( error = error, debugMessage = debugMessage, messageOverride = "Receipt validation failed: ${debugMessage ?: "unknown reason"}" ) ) }ios/HybridRnIap.swift (1)
319-356: Don’t downgrade.developerErrorinto.receiptFailedfor missingapple.sku.
Right now, a missing/invalidapple.skubecomes a receipt failure due to the broadcatch, which makes debugging/migration harder.@@ - } catch { - RnIapLog.failure("validateReceiptIOS", error: error) - throw PurchaseError.make(code: .receiptFailed, message: error.localizedDescription) - } + } catch let purchaseError as PurchaseError { + RnIapLog.failure("validateReceiptIOS", error: purchaseError) + throw purchaseError + } catch { + RnIapLog.failure("validateReceiptIOS", error: error) + throw PurchaseError.make(code: .receiptFailed, message: error.localizedDescription) + }
🧹 Nitpick comments (6)
example/screens/SubscriptionFlow.tsx (2)
1559-1573: Guard Android local verification whenpurchaseToken(or realaccessToken) is missing.
As written, Android can hit a hard failure ifpurchase.purchaseTokenis empty, because native validation requiresgoogle.purchaseToken.@@ - const result = await verifyPurchase({ + if (Platform.OS === 'android' && !purchase.purchaseToken) { + throw new Error('Missing purchaseToken (Android) for local verification'); + } + const result = await verifyPurchase({ apple: {sku: productId}, google: { sku: productId, // NOTE: accessToken must be obtained from your backend server // that has authenticated with Google Play Developer API accessToken: 'YOUR_OAUTH_ACCESS_TOKEN', packageName: 'dev.hyo.martie', purchaseToken: purchase.purchaseToken ?? '', isSub: true, }, });
1763-1771: Logging tweaks look fine; keep string assumptions safe forjsonRepresentation.
IfrenewalInfoIOS.jsonRepresentationisn’t guaranteed to be a string at runtime, considerString(...)beforesubstring(...)to avoid a crash in the example.Also applies to: 1794-1797, 1881-1885
example-expo/app/subscription-flow.tsx (1)
1564-1578: Avoid “pass all platforms” in the example; build options per-platform to prevent placeholder tokensThe comment says the library handles platform detection, but this sample hardcodes
google.accessTokenand may pass emptypurchaseToken(purchase.purchaseToken ?? ''). In practice, Android validation requires non-emptygoogle.*fields and developers shouldn’t need to ship placeholder secrets. Consider constructing options viaPlatform.OSand only including the relevant branch. As per coding guidelines, usePlatform.OSchecks for platform-specific logic in RN code.- const result = await verifyPurchase({ - apple: {sku: productId}, - google: { - sku: productId, - accessToken: 'YOUR_OAUTH_ACCESS_TOKEN', - packageName: 'dev.nicklasw.expoiapexample', - purchaseToken: purchase.purchaseToken ?? '', - isSub: true, - }, - // horizon: { sku: productId, userId: '...', accessToken: '...' } - }); + const result = await verifyPurchase( + Platform.OS === 'ios' + ? {apple: {sku: productId}} + : { + google: { + sku: productId, + accessToken: 'YOUR_OAUTH_ACCESS_TOKEN', + packageName: 'dev.nicklasw.expoiapexample', + purchaseToken: purchase.purchaseToken ?? '', + isSub: true, + }, + }, + );example/screens/PurchaseFlow.tsx (1)
480-494: Same concern: prefer platform-scopedverifyPurchaseoptions over placeholdersThis sample currently encourages providing both
appleandaccessToken/emptypurchaseTokenis acceptable. As per coding guidelines, usePlatform.OSchecks for platform-specific logic in RN code.example-expo/app/purchase-flow.tsx (1)
485-499: Mirror the per-platform options pattern to avoid shipping placeholdergoogle.accessTokenSame as the other examples: this encourages a “pass everything” payload with placeholder OAuth token. Prefer a
Platform.OS-scoped options object (and avoid suggesting emptypurchaseToken). As per coding guidelines, usePlatform.OSchecks for platform-specific logic in RN code.docs/blog/2025-12-11-release-14.6.0-billing-programs.md (1)
84-137: Docs claim “no need for Platform.OS checks”; ensure that’s actually true (and doesn’t conflict with Horizon support)The “After (14.6.0)” snippet instructs devs to “Provide all platform options,” but in practice:
- iOS callers won’t naturally have valid
google.accessToken/packageName/purchaseToken.- If Horizon is meant for Quest (Android), the current JS validation (and possibly native) must permit
horizonwithout requiringgoogle.*.Recommend tweaking the docs to show:
- Minimal iOS usage (
{apple: {sku: ...}})- Minimal Android usage (
{google: {...}})- Optional Horizon usage, clarifying when it applies and what fields are required.
Also applies to: 223-251
📜 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 (16)
.vscode/settings.json(1 hunks)PR_SUMMARY.md(0 hunks)android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt(2 hunks)docs/blog/2025-12-11-release-14.6.0-billing-programs.md(2 hunks)docs/docs/getting-started/setup-ios.md(2 hunks)example-expo/app/purchase-flow.tsx(1 hunks)example-expo/app/subscription-flow.tsx(1 hunks)example/screens/PurchaseFlow.tsx(1 hunks)example/screens/SubscriptionFlow.tsx(4 hunks)ios/HybridRnIap.swift(1 hunks)openiap-versions.json(1 hunks)src/__tests__/index.test.ts(2 hunks)src/hooks/useIAP.ts(2 hunks)src/index.ts(1 hunks)src/specs/RnIap.nitro.ts(2 hunks)src/types.ts(5 hunks)
💤 Files with no reviewable changes (1)
- PR_SUMMARY.md
🧰 Additional context used
📓 Path-based instructions (7)
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.tssrc/__tests__/index.test.tssrc/specs/RnIap.nitro.tssrc/hooks/useIAP.tssrc/types.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use type-only imports when importing types (import type).
Files:
src/index.tsexample/screens/SubscriptionFlow.tsxexample-expo/app/subscription-flow.tsxsrc/__tests__/index.test.tssrc/specs/RnIap.nitro.tsexample-expo/app/purchase-flow.tsxexample/screens/PurchaseFlow.tsxsrc/hooks/useIAP.tssrc/types.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.tsexample/screens/SubscriptionFlow.tsxexample-expo/app/subscription-flow.tsxsrc/__tests__/index.test.tssrc/specs/RnIap.nitro.tsexample-expo/app/purchase-flow.tsxexample/screens/PurchaseFlow.tsxsrc/hooks/useIAP.tssrc/types.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.swiftandroid/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
{example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}}
📄 CodeRabbit inference engine (CLAUDE.md)
{example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}}: In useIAP hook usage, do not expect returned data from methods that return Promise; consume state from the hook instead.
Do not call parseErrorStringToJsonObj() in app/user code; errors are already normalized by the library.
Files:
example/screens/SubscriptionFlow.tsxexample-expo/app/subscription-flow.tsxexample-expo/app/purchase-flow.tsxexample/screens/PurchaseFlow.tsx
**/*.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
src/types.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Never edit src/types.ts manually; it is generated. Import canonical types from this file instead of defining ad‑hoc interfaces.
Files:
src/types.ts
🧠 Learnings (7)
📓 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.
📚 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.tsexample/screens/SubscriptionFlow.tsxexample-expo/app/subscription-flow.tsxsrc/__tests__/index.test.tssrc/specs/RnIap.nitro.tsexample-expo/app/purchase-flow.tsxdocs/blog/2025-12-11-release-14.6.0-billing-programs.mdexample/screens/PurchaseFlow.tsxsrc/hooks/useIAP.tssrc/types.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.tsios/HybridRnIap.swiftexample/screens/SubscriptionFlow.tsxexample-expo/app/subscription-flow.tsxsrc/__tests__/index.test.tssrc/specs/RnIap.nitro.tsexample-expo/app/purchase-flow.tsxexample/screens/PurchaseFlow.tsxandroid/src/main/java/com/margelo/nitro/iap/HybridRnIap.ktsrc/hooks/useIAP.tsdocs/docs/getting-started/setup-ios.mdsrc/types.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:
example/screens/SubscriptionFlow.tsxexample-expo/app/subscription-flow.tsxsrc/__tests__/index.test.tsexample-expo/app/purchase-flow.tsxdocs/blog/2025-12-11-release-14.6.0-billing-programs.mdexample/screens/PurchaseFlow.tsxsrc/hooks/useIAP.tsdocs/docs/getting-started/setup-ios.mdsrc/types.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:
example/screens/SubscriptionFlow.tsxexample-expo/app/subscription-flow.tsxsrc/__tests__/index.test.tssrc/specs/RnIap.nitro.tsexample-expo/app/purchase-flow.tsxexample/screens/PurchaseFlow.tsxandroid/src/main/java/com/margelo/nitro/iap/HybridRnIap.ktsrc/types.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:
example-expo/app/subscription-flow.tsxsrc/__tests__/index.test.tsexample-expo/app/purchase-flow.tsxexample/screens/PurchaseFlow.tsxsrc/hooks/useIAP.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 **/*.nitro.ts : After modifying any .nitro.ts interface files, regenerate Nitro bridge files (yarn specs).
Applied to files:
src/specs/RnIap.nitro.ts
🧬 Code graph analysis (8)
src/index.ts (3)
ios/HybridRnIap.swift (1)
validateReceipt(319-356)src/types.ts (1)
MutationField(1307-1312)src/specs/RnIap.nitro.ts (1)
NitroReceiptValidationParams(88-92)
example/screens/SubscriptionFlow.tsx (3)
android/src/main/java/com/margelo/nitro/iap/RnIapLog.kt (1)
result(14-16)ios/RnIapLog.swift (1)
result(41-43)src/index.ts (1)
verifyPurchase(1482-1482)
example-expo/app/subscription-flow.tsx (3)
android/src/main/java/com/margelo/nitro/iap/RnIapLog.kt (1)
result(14-16)ios/RnIapLog.swift (1)
result(41-43)src/index.ts (1)
verifyPurchase(1482-1482)
src/specs/RnIap.nitro.ts (1)
src/types.ts (3)
VerifyPurchaseAppleOptions(1117-1120)VerifyPurchaseGoogleOptions(1128-1145)VerifyPurchaseHorizonOptions(1154-1164)
example-expo/app/purchase-flow.tsx (3)
android/src/main/java/com/margelo/nitro/iap/RnIapLog.kt (1)
result(14-16)ios/RnIapLog.swift (1)
result(41-43)src/index.ts (1)
verifyPurchase(1482-1482)
example/screens/PurchaseFlow.tsx (3)
android/src/main/java/com/margelo/nitro/iap/RnIapLog.kt (1)
result(14-16)ios/RnIapLog.swift (1)
result(41-43)src/index.ts (1)
verifyPurchase(1482-1482)
android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt (2)
src/types.ts (2)
VerifyPurchaseGoogleOptions(1128-1145)VerifyPurchaseProps(1173-1180)src/specs/RnIap.nitro.ts (1)
NitroReceiptValidationResultAndroid(236-255)
src/hooks/useIAP.ts (2)
src/types.ts (1)
VerifyPurchaseProps(1173-1180)src/index.ts (1)
validateReceipt(1363-1470)
🪛 detekt (1.23.8)
android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
[warning] 1200-1200: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: build-android
- GitHub Check: build-ios
- GitHub Check: example-expo
🔇 Additional comments (20)
.vscode/settings.json (1)
27-35: Spell-check updates look good.The cSpell.words array now uses a multi-line format for better readability and git diffs. The new entries ("iapkit", "martie") align with the PR's platform-specific IAP verification changes, and the alphabetically sorted list is well-organized.
android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt (1)
30-32: Imports look aligned with the new platform-specific verifyPurchase flow.
Just make sure these types exist in the bumped OpenIAP versions.src/__tests__/index.test.ts (2)
827-828: Test update matches new iOSapple.skucontract.
861-866: Test update matches new Androidgoogle.*contract (incl.purchaseToken).openiap-versions.json (1)
2-4: No action required — OpenIAP versions 1.3.2, 1.3.14, and 1.3.4 exist and contain the expected APIs.The patch bumps align with the documented 14.6.0 release. The comparison links in the blog post confirm that
apple-v1.3.2,google-v1.3.14, andgql-v1.3.4tags exist in the hyodotdev/openiap monorepo. All mentioned APIs (VerifyPurchaseGoogleOptions,VerifyPurchaseProps,verifyPurchase) are present in src/types.ts and actively used throughout the codebase (src/index.ts, src/hooks/useIAP.ts, example screens). The types are auto-generated from the OpenIAP GraphQL schema, as documented in scripts/update-types.mjs.docs/docs/getting-started/setup-ios.md (1)
55-60: The documentation examples are correct and consistent with the v14.6+ API. Both code samples properly pass the product SKU (purchase.productId) toapple.skuin thevalidateReceiptfunction, which is the intended behavior. No changes are needed.src/specs/RnIap.nitro.ts (6)
17-19: LGTM! Type-only imports for platform-specific verification options.The imports correctly use the
import typesyntax per coding guidelines, and the new platform-specific types align with the canonical types insrc/types.ts.
70-72: LGTM!The interface correctly maps the single
skufield fromVerifyPurchaseAppleOptions.
74-80: LGTM!All fields from
VerifyPurchaseGoogleOptionsare correctly mapped with proper optional handling forisSub.
82-86: LGTM!All fields from
VerifyPurchaseHorizonOptionsare correctly mapped for Meta Quest verification support.
88-92: LGTM! Platform-specific receipt validation params structure.The updated structure correctly supports platform-specific options. The optional + nullable pattern (
Type | null) is consistent with other Nitro interfaces in this file.
68-92: Reminder: Regenerate Nitro bridge files after this change.As per coding guidelines, after modifying
.nitro.tsinterface files, runyarn specsto regenerate the Nitro bridge files.src/types.ts (8)
920-937: LGTM! Clear documentation for platform-specific purchase parameters.The JSDoc comment helpfully clarifies the distinction between SDK/OS level (apple, google) and store, and explains the build-time determination for Horizon targeting.
974-991: LGTM!Consistent documentation pattern with
RequestPurchasePropsByPlatforms.
1003-1016: LGTM!Clear documentation distinguishing Apple JWS-based verification from Google purchase token verification.
1113-1120: LGTM!Apple verification interface is appropriately simple, relying on the transaction identifier accessible via
purchase.id. Based on learnings,validateReceiptexpects a transaction identifier as parameter.
1122-1145: LGTM! Well-documented Google verification options with security warnings.Appropriate security annotations remind developers not to log sensitive
accessTokenandpurchaseTokenvalues. The rename fromproductTokentopurchaseTokenaligns with the PR objectives.
1147-1164: LGTM! New Meta Horizon verification support.Good addition for Meta Quest verification. The documentation helpfully includes the actual API endpoint (
POST https://graph.oculus.com/$APP_ID/verify_entitlement) and appropriate security warnings.
1166-1180: LGTM! Platform-agnostic verification props interface.Clean structure allowing callers to provide only the relevant platform options. The pattern aligns with
NitroReceiptValidationParamsin the Nitro spec.
1182-1182: LGTM! Horizon verification result type.The
VerifyPurchaseResultHorizoninterface correctly models Meta's verify_entitlement API response, and the union type is properly extended.Also applies to: 1205-1214
- Android/Kotlin: Use mapOf loop for google options validation (cleaner code)
- TypeScript: Use requiredFields array loop for google options validation
- Fix validateReceipt return type from Promise<any> to Promise<VerifyPurchaseResult>
- Add Horizon verification path support for Meta Quest devices
- Guard param construction to avoid emitting {sku: undefined}
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3104 +/- ##
==========================================
+ Coverage 63.92% 64.66% +0.74%
==========================================
Files 9 9
Lines 1616 1664 +48
Branches 535 559 +24
==========================================
+ Hits 1033 1076 +43
- Misses 576 582 +6
+ Partials 7 6 -1
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
- Add Deprecated Methods section with table linking old/new APIs - Add explicit anchor IDs to core-methods.md for proper navigation - Add missing tags to tags.yml (billing-programs, discounts, etc.) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
- Add iapkitApiKey to IapPluginProps for IAPKit verification - Android: Add meta-data to AndroidManifest.xml - iOS: Add IAPKitAPIKey to Info.plist - Add comprehensive tests for both platforms - Update expo-plugin.md documentation - Add Billing Library 8.2.0 release date to blog post - Remove deprecated iOS-Specific Features from setup-ios.md - Add detailed purchase flow comments to example screens
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
example/screens/SubscriptionFlow.tsx (1)
1594-1625: Avoid logging raw purchase payloads (may contain tokens).
JSON.stringify(purchaseData, null, 2)can includepurchaseToken/ receipt-ish fields depending on platform mappings. Since this is an example app, it’s especially likely folks will copy it verbatim.Safer pattern (similar to PurchaseFlow): redact before logging.
- console.log( - 'Full purchase data for plan detection:', - JSON.stringify(purchaseData, null, 2), - ); + const {purchaseToken: tokenToMask, ...rest} = purchaseData as any; + console.log('Purchase data for plan detection (redacted):', { + ...rest, + ...(tokenToMask ? {purchaseToken: 'hidden'} : {}), + });docs/docs/api/use-iap.md (2)
457-469: Docs still show legacyvalidateReceipt(purchase.productId)usage; update to the new signature.This snippet contradicts the new API and will mislead readers.
- onPurchaseSuccess: async (purchase) => { - // Validate receipt on iOS - const validation = await validateReceipt(purchase.productId); + onPurchaseSuccess: async (purchase) => { + // Validate purchase on iOS (new API) + const validation = await validateReceipt({apple: {sku: purchase.productId}}); if (validation.isValid) { unlockContent(purchase.productId); } },(If you intend to steer users to
verifyPurchaseinstead, use that in the example rather than the deprecated name.)
234-271: Hook “void-return” guidance vs examples: avoidawait fetchProducts(...)when teaching state-driven flow.You can keep
awaitfor sequencing, but the doc currently reads likeawait fetchProductsis how you “get” products. I’d switch tofetchProducts(...)and then referencesubscriptionsstate to stay consistent with the “Void-returning methods” section.
♻️ Duplicate comments (1)
src/index.ts (1)
1377-1389: Nice simplification of Google required-field checks.This matches the earlier reviewer suggestion to reduce repetitive
ifstatements.
🧹 Nitpick comments (5)
docs/docs/guides/expo-plugin.md (1)
89-96: Consider clarifying how to read the API key from native config.The comment on line 92 mentions "Or read from native config" but the example hardcodes the API key again. Since the purpose of configuring
iapkitApiKeyin the plugin is to inject it into native configs, it would be helpful to show how to retrieve it at runtime rather than duplicating it in code.plugin/__tests__/withIAP-ios.test.ts (1)
62-78: Consider adding type definitions for the mock config.The
as anytype assertion on line 77 could be avoided by defining a proper interface for the test config structure. This would improve type safety and make test maintenance easier.+ interface MockModResults { + contents: string; + manifest: any; + plist: Record<string, string>; + entitlements: Record<string, any>; + podfile: string; + } + + interface MockConfig { + modResults: MockModResults; + } + function makeConfig(options?: { gradle?: string; manifest?: any; plist?: any; entitlements?: any; podfile?: string; - }) { + }): MockConfig { return { modResults: { contents: options?.gradle ?? 'dependencies {\n}', manifest: options?.manifest ?? {manifest: {application: [{}]}}, plist: options?.plist ?? {}, entitlements: options?.entitlements ?? {}, podfile: options?.podfile ?? '', }, - } as any; + }; }example/screens/PurchaseFlow.tsx (1)
415-705: Use the standardized purchase error helpers in examples (avoid directErrorCodechecks).In
onPurchaseError, preferisUserCancelledError()+getUserFriendlyErrorMessage()(per repo guidelines forexample/**/*.{ts,tsx}), instead of checkingerror.code === ErrorCode.UserCancelled+ manual message composition.plugin/__tests__/withIAP-android.test.ts (1)
15-58: Good coverage for IAPKit API key AndroidManifest insertion; consider an “existing meta-data preserved” case.The new tests assert insertion and absence. A small extra assertion would harden behavior: if
application[0]['meta-data']already contains other entries, ensure the plugin appends/updates onlydev.iapkit.API_KEYwithout dropping existing meta-data.Also applies to: 75-85, 144-172
example/screens/SubscriptionFlow.tsx (1)
1848-1858: Prefer standardized error helpers in example error handling.Per the repo guidelines for
example/**/*, useisUserCancelledError()+getUserFriendlyErrorMessage()instead of ad-hoc suppression (e.g.,ErrorCode.ServiceErrortiming window) and rawerror.messagealerts.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
docs/blog/2025-12-11-release-14.6.0-billing-programs.md(4 hunks)docs/blog/tags.yml(1 hunks)docs/docs/api/methods/core-methods.md(5 hunks)docs/docs/api/types.md(1 hunks)docs/docs/api/use-iap.md(1 hunks)docs/docs/getting-started/setup-ios.md(1 hunks)docs/docs/guides/expo-plugin.md(1 hunks)docs/versioned_docs/version-14.5/api/methods/core-methods.md(3 hunks)example/screens/PurchaseFlow.tsx(12 hunks)example/screens/SubscriptionFlow.tsx(14 hunks)plugin/__tests__/withIAP-android.test.ts(3 hunks)plugin/__tests__/withIAP-ios.test.ts(1 hunks)plugin/src/withIAP.ts(4 hunks)src/hooks/useIAP.ts(2 hunks)src/index.ts(1 hunks)src/specs/RnIap.nitro.ts(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- docs/docs/api/types.md
🧰 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/hooks/useIAP.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/hooks/useIAP.tsplugin/__tests__/withIAP-ios.test.tsexample/screens/PurchaseFlow.tsxsrc/index.tsexample/screens/SubscriptionFlow.tsxplugin/__tests__/withIAP-android.test.tsplugin/src/withIAP.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/hooks/useIAP.tsexample/screens/PurchaseFlow.tsxsrc/index.tsexample/screens/SubscriptionFlow.tsx
{example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}}
📄 CodeRabbit inference engine (CLAUDE.md)
{example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}}: In useIAP hook usage, do not expect returned data from methods that return Promise; consume state from the hook instead.
Do not call parseErrorStringToJsonObj() in app/user code; errors are already normalized by the library.
Files:
example/screens/PurchaseFlow.tsxexample/screens/SubscriptionFlow.tsx
🧠 Learnings (7)
📓 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.
📚 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:
docs/blog/2025-12-11-release-14.6.0-billing-programs.mdsrc/specs/RnIap.nitro.tsdocs/docs/api/use-iap.mdsrc/hooks/useIAP.tsexample/screens/PurchaseFlow.tsxsrc/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.tsdocs/docs/api/use-iap.mdsrc/hooks/useIAP.tsexample/screens/PurchaseFlow.tsxsrc/index.tsexample/screens/SubscriptionFlow.tsxdocs/docs/getting-started/setup-ios.mddocs/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.tsexample/screens/PurchaseFlow.tsxexample/screens/SubscriptionFlow.tsxdocs/docs/getting-started/setup-ios.mddocs/docs/api/methods/core-methods.md
📚 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/hooks/useIAP.tsplugin/__tests__/withIAP-ios.test.tsdocs/docs/guides/expo-plugin.mdexample/screens/PurchaseFlow.tsxexample/screens/SubscriptionFlow.tsxplugin/__tests__/withIAP-android.test.tsdocs/docs/getting-started/setup-ios.mddocs/docs/api/methods/core-methods.md
📚 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/hooks/useIAP.tsexample/screens/PurchaseFlow.tsxsrc/index.tsexample/screens/SubscriptionFlow.tsxdocs/docs/getting-started/setup-ios.mddocs/docs/api/methods/core-methods.md
📚 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}} : Use Platform.OS checks for platform-specific logic in React Native code.
Applied to files:
docs/docs/api/methods/core-methods.md
🧬 Code graph analysis (6)
src/specs/RnIap.nitro.ts (1)
src/types.ts (3)
VerifyPurchaseAppleOptions(1117-1120)VerifyPurchaseGoogleOptions(1128-1145)VerifyPurchaseHorizonOptions(1154-1164)
src/hooks/useIAP.ts (1)
src/types.ts (2)
VerifyPurchaseProps(1173-1180)VerifyPurchaseResult(1182-1182)
example/screens/PurchaseFlow.tsx (3)
android/src/main/java/com/margelo/nitro/iap/RnIapLog.kt (1)
result(14-16)ios/RnIapLog.swift (1)
result(41-43)src/index.ts (1)
verifyPurchase(1483-1483)
src/index.ts (4)
android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt (1)
validateReceipt(1126-1212)ios/HybridRnIap.swift (1)
validateReceipt(319-356)src/types.ts (1)
MutationField(1307-1312)src/specs/RnIap.nitro.ts (1)
NitroReceiptValidationParams(88-92)
example/screens/SubscriptionFlow.tsx (6)
android/src/main/java/com/margelo/nitro/iap/RnIapLog.kt (1)
result(14-16)src/index.ts (2)
verifyPurchase(1483-1483)getActiveSubscriptions(1838-1903)android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt (1)
getActiveSubscriptions(506-559)ios/HybridRnIap.swift (1)
getActiveSubscriptions(252-267)example/src/utils/constants.ts (1)
SUBSCRIPTION_PRODUCT_IDS(17-20)example-expo/constants/products.ts (1)
SUBSCRIPTION_PRODUCT_IDS(17-17)
plugin/src/withIAP.ts (2)
android/src/main/java/com/margelo/nitro/iap/RnIapLog.kt (1)
result(14-16)ios/RnIapLog.swift (1)
result(41-43)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: build-android
- GitHub Check: example-expo
- GitHub Check: build-ios
🔇 Additional comments (14)
docs/blog/tags.yml (1)
110-174: LGTM!The new tags follow the established structure and naming conventions. They appropriately categorize the new platform support (Horizon OS, Meta Quest, VR) and verification features introduced in this PR.
docs/docs/getting-started/setup-ios.md (1)
57-60: LGTM!The updated API correctly uses the new platform-specific structure with
apple: {sku: purchase.productId}. UsingproductIdfor the SKU field is semantically correct, aligning with the PR's restructuring of verification options.docs/versioned_docs/version-14.5/api/methods/core-methods.md (1)
987-987: LGTM!Adding explicit anchor identifiers improves documentation navigation and enables stable deep linking to these Android-specific API sections.
plugin/__tests__/withIAP-ios.test.ts (1)
80-96: LGTM! Good coverage of IAPKit API key scenarios.The tests appropriately verify the three key behaviors: adding the API key when provided, not adding it when absent, and preserving existing keys.
docs/docs/api/use-iap.md (1)
342-419: Docs:validateReceiptsection looks aligned with the new options-object API.The platform-specific parameter breakdown and examples match the new
{ apple, google, horizon }shape.src/hooks/useIAP.ts (2)
74-79: LGTM! Type signature correctly updated with deprecation notice.The
validateReceiptsignature now properly:
- Accepts
VerifyPurchaseProps(platform-specific options)- Returns
Promise<VerifyPurchaseResult>(notPromise<any>)- Includes deprecation notice directing users to
verifyPurchaseThis addresses the previous type regression concern.
310-314: LGTM! Implementation correctly delegates to internal handler.The implementation properly forwards the platform-specific
VerifyPurchasePropstovalidateReceiptInternal, maintaining type safety through the call chain.plugin/src/withIAP.ts (3)
167-193: LGTM! Android IAPKit API key integration is well-structured.The implementation:
- Correctly injects
dev.iapkit.API_KEYmeta-data into AndroidManifest.xml- Includes idempotency checks to prevent duplicates
- Uses safe type assertions for the meta-data structure
- Follows Android conventions for meta-data key naming
429-451: LGTM! iOS IAPKit API key integration follows plugin patterns.The implementation:
- Correctly sets
IAPKitAPIKeyin Info.plist- Includes idempotency checks
- Follows iOS naming conventions (camelCase)
- Consistent with other iOS config plugin helpers
453-476: LGTM! Main plugin flow correctly integrates IAPKit API key for both platforms.The implementation:
- Passes
iapkitApiKeyto Android plugin (line 455)- Conditionally applies iOS Info.plist key (lines 462-464)
- Properly documents the feature in
IapPluginPropstype- References IAPKit documentation for users
docs/blog/2025-12-11-release-14.6.0-billing-programs.md (2)
84-136: LGTM! Breaking changes are clearly documented with migration examples.The documentation:
- Provides clear before/after comparison for the API restructure
- Correctly shows
skumoved into platform-specific options- Documents
androidOptions→- Documents
productToken→purchaseTokenrename- Shows new
horizonplatform support- Explains that Platform.OS checks are no longer needed
This will help users migrate smoothly.
223-251: LGTM! Type definitions are correctly documented.The type documentation:
- Accurately reflects the new platform-specific verification interfaces
- Includes all required fields with explanatory comments
- Shows the unified
VerifyPurchasePropsstructure- Matches the type definitions in
src/types.tssrc/specs/RnIap.nitro.ts (2)
68-92: LGTM! Nitro interfaces correctly map platform-specific verification types.The implementation:
- Uses indexed access types to reference canonical types from
src/types.ts- Maintains type safety through the Nitro bridge
- Correctly separates Apple, Google, and Horizon verification options
- Uses
| nullfor Nitro compatibility with optional fieldsThis ensures the Nitro bridge stays in sync with the TypeScript API surface.
838-846: LGTM! Deprecation notice properly added to Nitro spec.The
validateReceiptmethod:
- Includes clear deprecation notice directing users to
verifyPurchase- Maintains backward compatibility with existing signature
- Correctly types platform-specific parameters and results
Consistent with deprecation strategy across the codebase.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
docs/blog/2025-12-11-release-14.6.0-billing-programs.md (1)
165-165: Consolidate duplicate "New Types" section headers.The document has two "## New Types" headers (lines 165 and 223) which creates redundancy in the table of contents and structure. While the subsections are appropriately organized, this could be clarified by renaming the second header or merging the sections under a single hierarchy.
Consider renaming line 223 to something more specific like
## Platform-Specific Verification Typesor similar to distinguish the VerifyPurchase types from the Billing Programs types documented above.Also applies to: 223-223
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
README.md(1 hunks)docs/blog/2025-12-11-release-14.6.0-billing-programs.md(4 hunks)
✅ Files skipped from review due to trivial changes (1)
- README.md
🧰 Additional context used
🧠 Learnings (2)
📓 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.
📚 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:
docs/blog/2025-12-11-release-14.6.0-billing-programs.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: example-expo
- GitHub Check: build-android
- GitHub Check: build-ios
🔇 Additional comments (3)
docs/blog/2025-12-11-release-14.6.0-billing-programs.md (3)
84-137: Breaking changes documentation is clear and well-structured.The Before/After examples effectively illustrate the API restructuring. The key changes are explicitly listed:
skurelocation to platform-specific optionsandroidOptions→productToken→purchaseTokenrename- New
horizonoption for Meta QuestThe examples match the type definitions provided in lines 228-250, and the migration path is unambiguous.
223-251: Platform-specific type definitions align with breaking changes examples.The three new interfaces (
VerifyPurchaseAppleOptions,VerifyPurchaseGoogleOptions,VerifyPurchaseHorizonOptions) and the container type (VerifyPurchaseProps) correctly reflect the API restructuring documented in the breaking changes section. Field names and optional markers are appropriate.
253-264: Deprecation notices properly document the migration path.The deprecated methods table clearly maps old APIs to their replacements and notes that deprecated methods will be removed in a future major version. This provides users with both a clear migration path and timeline, which is appropriate for a breaking change release.
- Add prominent security warning for accessToken in PurchaseFlow.tsx - Fix horizon validation to require userId and accessToken when sku is provided
- Set lastVersion to 'current' (14.6) in docusaurus config - Move 14.5 to versioned path /docs/14.5/ - Update deprecated methods links to point to 14.5 docs - Update installation section (no longer early access)
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
example/screens/PurchaseFlow.tsx (1)
543-563: Add runtime guard to prevent shipping placeholder credentials.While the security warning at lines 543-549 is improved, the placeholder
accessToken: 'YOUR_OAUTH_ACCESS_TOKEN'at line 555 remains in the codebase without runtime protection. Developers might accidentally ship this placeholder.Add a runtime assertion that fails fast in development:
const result = await verifyPurchase({ apple: {sku: productId}, google: { sku: productId, - // PLACEHOLDER - Replace with token fetched from your backend - accessToken: 'YOUR_OAUTH_ACCESS_TOKEN', + // PLACEHOLDER - Replace with token fetched from your backend + accessToken: + __DEV__ && 'YOUR_OAUTH_ACCESS_TOKEN'.includes('YOUR_OAUTH') + ? (() => { + throw new Error( + 'SECURITY: Replace YOUR_OAUTH_ACCESS_TOKEN with real token from backend', + ); + })() + : 'YOUR_OAUTH_ACCESS_TOKEN', packageName: 'dev.hyo.martie', purchaseToken: purchase.purchaseToken ?? '', isSub: false, },Alternatively, use an environment variable:
const accessToken = process.env.GOOGLE_OAUTH_TOKEN; if (!accessToken) { throw new Error('Missing GOOGLE_OAUTH_TOKEN - fetch from your backend'); }Based on learnings: This is example code showing the new API structure, so a clear runtime error helps prevent copy-paste mistakes.
🧹 Nitpick comments (2)
src/index.ts (1)
1363-1397: Add explicit unsupported platform validation.The validation logic correctly handles iOS and Android (with Horizon and Google paths), but doesn't explicitly validate against unsupported platforms. While the native layer will likely fail, an early JavaScript error would provide better developer experience.
Add an explicit check after the Android validation:
} } + } else { + throw new Error( + `Unsupported platform: ${Platform.OS}. verifyPurchase only supports iOS and Android.`, + ); }This provides clearer error messages during development and prevents confusion on unsupported platforms.
As per coding guidelines: Use Platform.OS checks for platform-specific logic in React Native code.
docs/blog/2025-12-11-release-14.6.0-billing-programs.md (1)
223-252: Consolidate or rename duplicate "New Types" sections.There are two "## New Types" headings in the document:
- Line 165: Billing Programs Types
- Line 223: VerifyPurchase Platform-Specific Types
Consider one of these approaches:
Option 1: Use a single "New Types" section with subsections:
## New Types ### Billing Programs Types [content from line 165 section] ### VerifyPurchase Platform-Specific Types [content from line 223 section]Option 2: Use distinct level-2 headings:
## Billing Programs Types [content from line 165 section] ## VerifyPurchase Types [content from line 223 section]Either approach would improve document structure and navigation.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
docs/blog/2025-12-11-release-14.6.0-billing-programs.md(3 hunks)docs/docusaurus.config.ts(1 hunks)example/screens/PurchaseFlow.tsx(12 hunks)src/index.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use type-only imports when importing types (import type).
Files:
example/screens/PurchaseFlow.tsxsrc/index.tsdocs/docusaurus.config.ts
{example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}}
📄 CodeRabbit inference engine (CLAUDE.md)
{example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}}: In useIAP hook usage, do not expect returned data from methods that return Promise; consume state from the hook instead.
Do not call parseErrorStringToJsonObj() in app/user code; errors are already normalized by the library.
Files:
example/screens/PurchaseFlow.tsx
{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:
example/screens/PurchaseFlow.tsxsrc/index.ts
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
🧠 Learnings (7)
📓 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.
📚 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:
example/screens/PurchaseFlow.tsxsrc/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:
example/screens/PurchaseFlow.tsx
📚 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:
example/screens/PurchaseFlow.tsxdocs/blog/2025-12-11-release-14.6.0-billing-programs.mdsrc/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:
example/screens/PurchaseFlow.tsxsrc/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}} : Use Platform.OS checks for platform-specific logic in React Native code.
Applied to files:
example/screens/PurchaseFlow.tsx
📚 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:
example/screens/PurchaseFlow.tsx
🧬 Code graph analysis (2)
example/screens/PurchaseFlow.tsx (1)
src/index.ts (1)
verifyPurchase(1489-1489)
src/index.ts (3)
android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt (1)
validateReceipt(1126-1212)ios/HybridRnIap.swift (1)
validateReceipt(319-356)src/specs/RnIap.nitro.ts (1)
NitroReceiptValidationParams(88-92)
⏰ 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). (4)
- GitHub Check: codecov/patch
- GitHub Check: example-expo
- GitHub Check: build-ios
- GitHub Check: build-android
🔇 Additional comments (8)
docs/docusaurus.config.ts (1)
50-60: LGTM! Version promotion correctly implemented.The documentation versioning has been properly updated to promote 14.6 from "Next" to "Current" and archive 14.5 as a historical version. The
lastVersion, labels, paths, and banner settings are all correctly configured for the new release structure.example/screens/PurchaseFlow.tsx (2)
415-453: Excellent documentation of the purchase flow.The 6-step flow documentation is clear, comprehensive, and provides excellent guidance for developers implementing IAP. The structure helps users understand the complete lifecycle from connection through transaction completion.
757-800: Clear documentation and correct cross-platform implementation.The Step 3 documentation (lines 757-775) clearly explains the three options for requesting purchases, and the implementation correctly uses Option C (cross-platform request structure). This aligns well with the library's design pattern of providing all platform options and letting the library handle platform detection.
src/index.ts (3)
1338-1359: Clear deprecation notice and migration example.The updated JSDoc provides a clear deprecation notice and demonstrates the correct usage of the new platform-specific API structure. This helps developers understand the migration path from the old API to the new one.
1399-1426: Correct conditional parameter construction.The parameter construction logic properly prevents partial objects by only including platform-specific options when all required fields are present. This addresses previous concerns about sending
horizon: nullwhen only partial Horizon data was provided.The pattern of checking all fields before construction (e.g., lines 1406-1409 for Google) ensures type safety and prevents runtime errors in the native layer.
1489-1548: Clean implementation of verification methods.The
verifyPurchasealias (line 1489) provides a consistent API surface, andverifyPurchaseWithProvider(lines 1512-1548) properly validates the provider and constructs results. The provider validation (lines 1521-1526) ensures only supported providers are used, and error handling follows the established patterns in the codebase.docs/blog/2025-12-11-release-14.6.0-billing-programs.md (2)
84-137: Comprehensive breaking changes documentation with clear migration path.The breaking changes section effectively documents the API restructuring with before/after examples and a clear list of key changes. The side-by-side comparison (lines 90-128) makes it easy for developers to understand how to migrate their code.
253-264: No issues found. The deprecated methods table correctly links to the versioned 14.5 documentation for the old methods and current documentation for their replacements. All referenced anchor IDs exist in their target files and will resolve properly.
BREAKING CHANGE: verifyPurchase/validateReceipt API now uses platform-specific options
skufrom root level into platform options (apple.sku, google.sku, horizon.sku)androidOptionstogoogleproductTokentopurchaseTokenin google optionshorizonoptions for Meta Quest verificationSummary by CodeRabbit
Breaking Changes
New Features
Chores
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.