feat: add verifyPurchaseWithProvider for IAPKit verification#3100
Conversation
- Add verifyPurchaseWithProvider function to verify purchases with external providers (IAPKit) - Add hasActiveSubscriptions helper function - Update types: rename ReceiptValidation* to VerifyPurchase* for consistency with OpenIAP - Add verification method selection UI to example screens (PurchaseFlow, SubscriptionFlow) - Configure react-native-dotenv for IAPKIT_API_KEY environment variable - Fix iOS: use stringValue instead of rawValue for Nitro enum serialization - Fix Android: convert Nitro enum to lowercase string for OpenIAP compatibility - Add enum mapping helper functions for Android (mapIapkitPurchaseState, mapIapkitStore, mapPurchaseVerificationProvider)
|
Warning Rate limit exceeded@hyochan has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 0 minutes and 44 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (4)
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 provider-based purchase verification (IAPKit) and active-subscription checks across Android and iOS native bridges, expands verification types and public API (verifyPurchase / verifyPurchaseWithProvider / hasActiveSubscriptions), updates error-code mapping, and wires example app configuration and UI for verification selection. Changes
Sequence Diagram(s)sequenceDiagram
participant App as Example App
participant Hook as useIAP / UI
participant Bridge as HybridRnIap (Kotlin/Swift)
participant OpenIAP as OpenIAP Module
participant Provider as IAPKit Provider
App->>Hook: user triggers verification (iapkit)
Hook->>Bridge: verifyPurchaseWithProvider(params)
activate Bridge
Bridge->>OpenIAP: verifyPurchaseWithProvider(openIapProps)
activate OpenIAP
OpenIAP->>Provider: send verification request (token/jws + apiKey)
Provider-->>OpenIAP: verification response (isValid, state, store)
OpenIAP-->>Bridge: mapped OpenIAP result
deactivate OpenIAP
Bridge-->>Hook: NitroVerifyPurchaseWithProviderResult
deactivate Bridge
Hook->>App: display verification result / alert
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)
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 significantly enhances the in-app purchase (IAP) module by integrating robust external purchase verification capabilities through IAPKit. It introduces a new API for provider-based verification, refactors existing receipt validation types for better clarity and consistency, and provides a convenient helper for checking active subscriptions. The example application has also been updated to demonstrate these new features, including a user interface for selecting verification methods and proper environment variable configuration. 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 introduces a significant new feature for purchase verification with external providers like IAPKit, along with a new hasActiveSubscriptions helper function. The changes are well-structured, including necessary updates to native Android and iOS code, TypeScript types, and the example application. A key improvement is the renaming of ReceiptValidation* types to VerifyPurchase* for better consistency with OpenIAP.
My review focuses on a critical type-safety issue in the TypeScript definitions that could lead to runtime errors, and I've also identified opportunities to reduce code duplication in the example app by refactoring shared logic into utility functions or custom hooks. Addressing these points will improve the robustness and maintainability of the new functionality.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (5)
example/src/types/env.d.ts (1)
1-3: Consider typing asstring | undefinedto reflect runtime behavior.The babel config sets
allowUndefined: true, meaningIAPKIT_API_KEYcould beundefinedat runtime if the.envfile is missing or the key is not set. The currentstringtype may mask this, leading to runtime errors when consuming code assumes a non-null value.declare module '@env' { - export const IAPKIT_API_KEY: string; + export const IAPKIT_API_KEY: string | undefined; }ios/HybridRnIap.swift (1)
319-350: verifyPurchaseWithProvider mapping looks correct; consider tightening enum fallbackThe new
verifyPurchaseWithProviderflow correctly:
- Logs provider via
params.provider.stringValue(string enum) rather than raw numeric values.- Builds OpenIAP
VerifyPurchaseWithProviderPropsvia JSON serialization.- Maps
result.iapkitandresult.providerback into Nitro enums using string raw values.One minor robustness improvement:
PurchaseVerificationProvider(fromString: result.provider.rawValue) ?? .iapkitsilently maps unknown providers to.iapkit. If additional providers are added later, this could misrepresent the actual provider. Prefer either:
- Throwing on unknown values, or
- Falling back to a neutral sentinel (e.g.
.none) rather than hard‑coding.iapkit.Also applies to: 352-394
src/index.ts (1)
1769-1834: Consider delegating hasActiveSubscriptions to the new native bridge for efficiency
getActiveSubscriptionsnow goes through the nativegetActiveSubscriptionsbridge and builds richActiveSubscriptionobjects, whilehasActiveSubscriptionsstill derives its result by fetching and mapping all active subscriptions:const activeSubscriptions = await getActiveSubscriptions(subscriptionIds); return activeSubscriptions.length > 0;Now that
RnIapexposes a nativehasActiveSubscriptions(subscriptionIds?: string[]): Promise<boolean>, you could avoid the extra allocation and mapping by delegating directly:export const hasActiveSubscriptions: QueryField<'hasActiveSubscriptions'> = async ( subscriptionIds, ) => { try { return await IAP.instance.hasActiveSubscriptions(subscriptionIds ?? undefined); } catch (error) { RnIapConsole.warn('Error checking active subscriptions:', error); return false; } };Not mandatory, but this would better leverage the new bridge and reduce overhead on hot paths.
Also applies to: 1928-1939
example/screens/SubscriptionFlow.tsx (1)
1363-1371: Tighten IAPKit verification payload to be platform‑specific and reuse existing helpers where possibleThe post‑purchase verification flow is well‑structured (optional based on
verificationMethod, good logging, redaction, and guarded byIAPKIT_API_KEYand token checks). Two refinements would make it more robust:
- Only send the relevant platform block to IAPKit
Right now both
appleandjwsOrToken:const verifyRequest: VerifyPurchaseWithProviderProps = { provider: 'iapkit', iapkit: { apiKey, apple: { jws: jwsOrToken }, google: { purchaseToken: jwsOrToken }, }, };Prefer branching on
Platform.OSso the payload matches what the backend expects:const base: VerifyPurchaseWithProviderProps = { provider: 'iapkit' }; const verifyRequest: VerifyPurchaseWithProviderProps = Platform.OS === 'ios' ? { ...base, iapkit: { apiKey, apple: { jws: jwsOrToken }, }, } : { ...base, iapkit: { apiKey, google: { purchaseToken: jwsOrToken }, }, };
- Optional: standardize error messaging
The manual extraction of
errorMessageworks, but if verification errors surface normalized IAP error shapes, consider using the library’s existing helpers (e.g.getUserFriendlyErrorMessage) in the future to keep messaging consistent across flows. (Only if those helpers are already exported for app code.)Also applies to: 1425-1562
example/screens/PurchaseFlow.tsx (1)
411-418: Align IAPKit verification payload with the current platform and consider sharing the verification helperThe IAPKit verification logic in
onPurchaseSuccessclosely matches SubscriptionFlow’s and generally looks solid (good masking/logging, configuration checks, and user alerts). Two incremental improvements:
- Platform‑specific payload, as in SubscriptionFlow suggestion
Currently both
appleandjwsOrToken. Restricting the payload to the active platform (onlyapple.jwson iOS, onlygoogle.purchaseTokenon Android) will better match the expected contract and avoid surprising the backend.
- Optional: extract shared verification helper
The verification block here and in
SubscriptionFlowContainerare nearly identical. Consider extracting a small helper (e.g.runPostPurchaseVerification({ purchase, verificationMethod })) or custom hook in the example app to keep the two flows in sync and reduce duplication.Also applies to: 450-573
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
example/ios/Podfile.lockis excluded by!**/*.lockyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (15)
android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt(4 hunks)example/.env.example(1 hunks)example/.gitignore(1 hunks)example/babel.config.js(1 hunks)example/package.json(1 hunks)example/screens/PurchaseFlow.tsx(10 hunks)example/screens/SubscriptionFlow.tsx(11 hunks)example/src/types/env.d.ts(1 hunks)ios/HybridRnIap.swift(3 hunks)openiap-versions.json(1 hunks)src/hooks/useIAP.ts(5 hunks)src/index.ts(5 hunks)src/specs/RnIap.nitro.ts(6 hunks)src/types.ts(12 hunks)src/utils/errorMapping.ts(4 hunks)
🧰 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/hooks/useIAP.tssrc/index.tssrc/utils/errorMapping.tssrc/specs/RnIap.nitro.tssrc/types.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use type-only imports when importing types (import type).
Files:
src/hooks/useIAP.tssrc/index.tsexample/src/types/env.d.tssrc/utils/errorMapping.tsexample/screens/PurchaseFlow.tsxsrc/specs/RnIap.nitro.tssrc/types.tsexample/screens/SubscriptionFlow.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:
src/hooks/useIAP.tssrc/index.tsexample/src/types/env.d.tssrc/utils/errorMapping.tsexample/screens/PurchaseFlow.tsxsrc/specs/RnIap.nitro.tssrc/types.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/src/types/env.d.tsexample/screens/PurchaseFlow.tsxexample/screens/SubscriptionFlow.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
{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:
android/src/main/java/com/margelo/nitro/iap/HybridRnIap.ktios/HybridRnIap.swift
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 (10)
📓 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:
openiap-versions.jsonsrc/hooks/useIAP.tssrc/index.tssrc/utils/errorMapping.tsexample/screens/PurchaseFlow.tsxsrc/specs/RnIap.nitro.tsandroid/src/main/java/com/margelo/nitro/iap/HybridRnIap.ktsrc/types.tsexample/screens/SubscriptionFlow.tsx
📚 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.tssrc/index.tsexample/screens/PurchaseFlow.tsxsrc/specs/RnIap.nitro.tssrc/types.tsexample/screens/SubscriptionFlow.tsx
📚 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/hooks/useIAP.tssrc/index.tsexample/screens/PurchaseFlow.tsxsrc/specs/RnIap.nitro.tsios/HybridRnIap.swiftsrc/types.tsexample/screens/SubscriptionFlow.tsx
📚 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.tssrc/index.tssrc/utils/errorMapping.tsexample/screens/PurchaseFlow.tsxsrc/types.tsexample/screens/SubscriptionFlow.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:
src/index.tsexample/screens/PurchaseFlow.tsxsrc/specs/RnIap.nitro.tsandroid/src/main/java/com/margelo/nitro/iap/HybridRnIap.ktsrc/types.tsexample/screens/SubscriptionFlow.tsx
📚 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: Always use yarn for package management in this repo (Yarn 3 workspaces); run typecheck and lint before committing.
Applied to files:
example/.gitignore
📚 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}} : Do not call parseErrorStringToJsonObj() in app/user code; errors are already normalized by the library.
Applied to files:
src/utils/errorMapping.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.tsxexample/screens/SubscriptionFlow.tsx
📚 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 (7)
src/hooks/useIAP.ts (2)
src/types.ts (4)
VerifyPurchaseProps(844-849)VerifyPurchaseResult(851-851)VerifyPurchaseWithProviderProps(885-888)VerifyPurchaseWithProviderResult(890-894)src/index.ts (2)
verifyPurchase(1423-1423)verifyPurchaseWithProvider(1446-1473)
src/index.ts (4)
src/types.ts (3)
VerifyPurchaseResultIOS(874-883)VerifyPurchaseResultAndroid(853-872)MutationField(958-963)src/utils/debug.ts (1)
RnIapConsole(18-49)src/utils/error.ts (1)
parseErrorStringToJsonObj(27-77)src/utils/errorMapping.ts (1)
createPurchaseError(115-134)
example/screens/PurchaseFlow.tsx (6)
src/hooks/useIAP.ts (1)
useIAP(118-476)src/index.ts (3)
useIAP(106-106)verifyPurchase(1423-1423)verifyPurchaseWithProvider(1446-1473)example/src/types/env.d.ts (1)
IAPKIT_API_KEY(2-2)src/types.ts (1)
VerifyPurchaseWithProviderProps(885-888)android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt (1)
verifyPurchaseWithProvider(1123-1174)ios/HybridRnIap.swift (1)
verifyPurchaseWithProvider(352-394)
src/specs/RnIap.nitro.ts (1)
src/types.ts (7)
IapPlatform(181-181)IapkitPurchaseState(184-184)IapkitStore(186-186)PurchaseVerificationProvider(549-549)VerifyPurchaseAndroidOptions(837-842)VerifyPurchaseProps(844-849)VerifyPurchaseResultAndroid(853-872)
ios/HybridRnIap.swift (4)
android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt (2)
hasActiveSubscriptions(553-578)verifyPurchaseWithProvider(1123-1174)src/index.ts (2)
hasActiveSubscriptions(1928-1939)verifyPurchaseWithProvider(1446-1473)src/types.ts (4)
VerifyPurchaseWithProviderProps(885-888)IapkitPurchaseState(184-184)IapkitStore(186-186)PurchaseVerificationProvider(549-549)src/specs/RnIap.nitro.ts (5)
NitroVerifyPurchaseWithIapkitResult(230-234)IapkitPurchaseState(30-39)IapkitStore(41-41)NitroVerifyPurchaseWithProviderResult(236-239)PurchaseVerificationProvider(44-44)
src/types.ts (1)
src/specs/RnIap.nitro.ts (3)
IapkitPurchaseState(30-39)IapkitStore(41-41)PurchaseVerificationProvider(44-44)
example/screens/SubscriptionFlow.tsx (7)
android/src/main/java/com/margelo/nitro/iap/RnIapLog.kt (1)
result(14-16)ios/RnIapLog.swift (2)
result(41-43)error(35-35)src/index.ts (2)
verifyPurchase(1423-1423)verifyPurchaseWithProvider(1446-1473)example/src/types/env.d.ts (1)
IAPKIT_API_KEY(2-2)src/types.ts (1)
VerifyPurchaseWithProviderProps(885-888)android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt (1)
verifyPurchaseWithProvider(1123-1174)ios/HybridRnIap.swift (1)
verifyPurchaseWithProvider(352-394)
🪛 detekt (1.23.8)
android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
[warning] 566-566: The caught exception is swallowed. The original exception could be lost.
(detekt.exceptions.SwallowedException)
[warning] 1162-1162: 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). (2)
- GitHub Check: build-android
- GitHub Check: build-ios
🔇 Additional comments (22)
openiap-versions.json (1)
2-4: LGTM!Version bumps align with the new
hasActiveSubscriptionsandverifyPurchaseWithProviderfeatures introduced in this PR. Based on learnings, these bumps will regenerate the types insrc/types.tsfrom the updated GraphQL schema.android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt (3)
553-578: LGTM!The
hasActiveSubscriptionsmethod follows the established patterns in this file: initializes connection, logs payload/result, delegates to OpenIAP, and maps exceptions consistently. The static analysis warning about swallowed exception at line 566 is a false positive - the exception is properly re-thrown asOpenIapExceptionwith context.
1123-1174: LGTM!The
verifyPurchaseWithProviderimplementation is well-structured:
- Correctly converts Nitro enum to lowercase string for OpenIAP compatibility
- Uses null-safe
letblocks for optional nested props- Follows established error handling pattern (re-throws as
OpenIapException)The static analysis warning about swallowed exception at line 1162 is a false positive - the exception is properly logged and re-thrown with context.
1407-1435: LGTM!The mapping helpers are well-implemented:
mapIapkitPurchaseStatecorrectly handles both underscore (PENDING_ACKNOWLEDGMENT) and kebab-case (PENDING-ACKNOWLEDGMENT) variationsmapIapkitStoredefaulting tomapPurchaseVerificationProvidersafely defaults toNONEfor unknown providerssrc/utils/errorMapping.ts (3)
14-25: LGTM!Good backward-compatibility approach for the naming migration from
Receipt*toPurchaseVerification*error codes. The aliases cover:
- Uppercase formats (
RECEIPT_FAILED,E_RECEIPT_FAILED)- Kebab-case formats (
receipt-failed)This ensures existing native code using old error code formats will map correctly to the new enum values.
337-345: LGTM!The fall-through pattern correctly groups deprecated
Receipt*codes with their newPurchaseVerification*equivalents, ensuring consistent user-facing messages during the migration period. As per coding guidelines, usinggetUserFriendlyErrorMessage()with normalizedErrorCodeis the preferred approach.
370-380: LGTM!The dual alias lookup correctly handles both case formats:
- Uppercase lookup (line 371) for
SCREAMING_SNAKE_CASEcodes from native- Lowercase lookup (line 377) for kebab-case codes like
'receipt-failed'This ensures comprehensive backward compatibility with various native error code formats.
src/types.ts (1)
1-4: LGTM!This is an auto-generated file from the OpenIAP GraphQL schema (as noted in the header and per coding guidelines). The new types (
IapkitPurchaseState,IapkitStore,VerifyPurchase*,RequestVerifyPurchaseWithIapkit*) align with theopeniap-gqlversion bump (1.2.5 → 1.2.7) and support the new IAPKit verification feature.example/.gitignore (1)
76-80: LGTM!Good addition for protecting sensitive environment variables like
IAPKIT_API_KEY. The patterns cover standard env file naming conventions (.env,.env.local,.env*.local).example/.env.example (1)
1-3: LGTM!Clear example configuration with helpful instructions for obtaining the API key. The placeholder value is obviously not a real key.
example/babel.config.js (1)
3-15: LGTM!The
react-native-dotenvplugin configuration is correct. Note thatallowUndefined: truemeans missing env variables won't cause build errors—ensure consuming code handles potentially undefined values gracefully (as flagged in the type declaration review).src/hooks/useIAP.ts (5)
17-18: LGTM!New verification functions imported correctly from the top-level module.
38-41: LGTM!Type-only imports used correctly for the new verification types, following the coding guidelines.
83-90: LGTM!Type signatures and JSDoc comments are clear and correctly define the new verification methods.
331-345: LGTM!The implementations correctly delegate to the top-level functions using
useCallbackwith empty dependencies, following the established pattern in this hook (e.g.,requestPurchase).
453-454: LGTM!New verification methods correctly exposed in the hook's return object.
ios/HybridRnIap.swift (1)
252-282: hasActiveSubscriptions implementation aligns with getActiveSubscriptions usageThe new
hasActiveSubscriptionswrapper mirrors thegetActiveSubscriptionspattern (connection check, payload/result logging, simple error propagation) and is consistent with the Android implementation. I don’t see functional issues here.src/index.ts (2)
30-32: Receipt verification result remapping matches new VerifyPurchase types*The updated
validateReceiptimplementation cleanly re-maps Nitro results into the newVerifyPurchaseResultIOS/VerifyPurchaseResultAndroidshapes, including convertinglatestTransactionviaconvertNitroPurchaseToPurchaseand normalizing Android’sdeferredSkutostring | null. This matches the definitions insrc/types.tsand looks sound.Also applies to: 1337-1400
1413-1423: verifyPurchase alias keeps API backward-compatibleExporting
verifyPurchaseas an alias ofvalidateReceiptmaintains OpenIAP naming while preserving existing behavior and types. No issues here.example/screens/SubscriptionFlow.tsx (1)
12-33: VerificationMethod wiring and UI are cohesive and platform‑awareThe introduction of
VerificationMethod, its inclusion inSubscriptionFlowProps, the header “Purchase Verification” selector, and the container’sverificationMethodstate/ref (with iOSActionSheetIOSvs AndroidAlert) all hang together cleanly. The use of a ref to avoid stale closures in async callbacks is a good touch.Also applies to: 214-232, 590-623, 1348-1355, 1854-1900
example/screens/PurchaseFlow.tsx (1)
11-12: Purchase verification selector and state management are consistent with SubscriptionFlowThe Purchase flow mirrors the Subscription flow’s verification concept (
VerificationMethodtype, props, header selector, container state/ref, and platform‑specific selector UI). This symmetry will make the example app much easier to reason about.Also applies to: 21-37, 41-53, 66-78, 190-207, 396-410, 674-720, 722-735
src/specs/RnIap.nitro.ts (1)
7-25: Nitro spec now aligns with VerifyPurchase and provider APIs; runyarn specsto regenerate bridges*The spec changes are internally consistent:
- Receipt validation params/options/results now use the
VerifyPurchase*types fromsrc/types.tsas a single source of truthNitroPurchase.platformandNitroProduct.platformuse the sharedIapPlatformunion matching public types- IAPKit provider types (
NitroVerifyPurchaseWithIapkit*,NitroVerifyPurchaseWithProvider*) match the shapes consumed/produced by Android and iOSverifyPurchaseWithProviderimplementations- New
hasActiveSubscriptionsandverifyPurchaseWithProviderentries onRnIapalign with native bridges and JS surfaceAfter modifying
src/specs/RnIap.nitro.ts, regenerate bridge files by runningyarn specsto keep native stubs in sync with this spec.
- Extract getErrorMessage utility to reduce code duplication - Extract useVerificationMethod hook for verification method selection - Update PurchaseFlow and SubscriptionFlow to use shared utilities - Fix openiap podspec to use exact version match 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3100 +/- ##
==========================================
- Coverage 63.00% 62.97% -0.03%
==========================================
Files 9 9
Lines 1538 1564 +26
Branches 515 522 +7
==========================================
+ Hits 969 985 +16
- Misses 563 573 +10
Partials 6 6
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Replace unsafe type cast with runtime validation to ensure provider is 'iapkit'. Throws DeveloperError if unsupported provider is returned. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Add documentation for verifyPurchaseWithProvider() function including usage examples, parameters, return types, and platform-specific behavior. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
- Add bookmark links to Unified APIs section in core-methods.md - Add comprehensive tests for verifyPurchaseWithProvider function: - iOS verification with JWS token - Android verification with purchase token - Provider validation (throws on unsupported provider) - Verification failure states (expired, etc.) - Error handling for native errors - Null iapkit param handling 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Add tests matching expo-iap test patterns: - Various IAPKit purchase states (all 9 states) - Inauthentic verification response - Ready-to-consume state for consumables - Pending-acknowledgment state for subscriptions 🤖 Generated with [Claude Code](https://claude.com/claude-code)
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/index.ts (1)
1446-1472: Unsafe type cast onresult.providerremains unaddressed.The cast
result.provider as 'iapkit'on line 1455 masks the fact that the native layer can return other provider values (e.g.,'none'). This was flagged in previous reviews. Either updatePurchaseVerificationProviderinsrc/types.tsto include all possible values ('iapkit' | 'none'), or add runtime validation before returning.return { - provider: result.provider as 'iapkit', + provider: result.provider, iapkit: result.iapkit.map((item) => ({This requires updating the type definition in
src/types.ts:export type PurchaseVerificationProvider = 'iapkit' | 'none';
🧹 Nitpick comments (6)
NitroIap.podspec (1)
45-46: Consider usingto_sinstead of string interpolation.The static analysis tool suggests using
apple_version.to_sinstead of"#{apple_version}"for cleaner code when no additional formatting is needed.# OpenIAP Apple for StoreKit 2 integration # Exact version match for consistent builds - s.dependency 'openiap', "#{apple_version}" + s.dependency 'openiap', apple_version.to_sexample/screens/PurchaseFlow.tsx (1)
495-506: BothappleandThe verification request includes both
apple.jwsandgoogle.purchaseTokenwith the same token value on every platform. Consider populating only the platform-specific field based onPlatform.OSto avoid potential confusion on the server side and to send only the relevant data.const verifyRequest: VerifyPurchaseWithProviderProps = { provider: 'iapkit', iapkit: { apiKey, - apple: { - jws: jwsOrToken, - }, - google: { - purchaseToken: jwsOrToken, - }, + ...(Platform.OS === 'ios' + ? { apple: { jws: jwsOrToken } } + : { google: { purchaseToken: jwsOrToken } }), }, };example/screens/SubscriptionFlow.tsx (4)
225-226: Verification method UI & wiring look good; optional reuse of label helperThe new
verificationMethod/onChangeVerificationMethodprops, the selector UI, and the wiring viauseVerificationMethodin the container are cohesive and keep selection state nicely centralized. The styles for the verification block also match the existing header aesthetic.If you want to avoid duplicating the mapping from
VerificationMethod→ label across screens, you could optionally reusegetVerificationMethodLabelfromuseVerificationMethod(e.g., pass a computed label down as a prop) while still decorating it with emojis here. Not required, just a small DRY/readability tweak.Also applies to: 233-234, 245-253, 608-624, 1848-1857, 1897-1926
23-33: Preferimport typefor type-only imports fromreact-native-iap
VerifyPurchaseWithProviderProps,ActiveSubscription,ProductSubscription,ProductSubscriptionAndroid,Purchase, andPurchaseErrorare used only as types. Per the repo's TypeScript guidelines, separate type-only imports usingimport type:-import { - requestPurchase, - useIAP, - deepLinkToSubscriptions, - type ActiveSubscription, - type ProductSubscription, - type ProductSubscriptionAndroid, - type Purchase, - type PurchaseError, - type VerifyPurchaseWithProviderProps, - ErrorCode, -} from 'react-native-iap'; +import { + requestPurchase, + useIAP, + deepLinkToSubscriptions, + ErrorCode, +} from 'react-native-iap'; +import type { + ActiveSubscription, + ProductSubscription, + ProductSubscriptionAndroid, + Purchase, + PurchaseError, + VerifyPurchaseWithProviderProps, +} from 'react-native-iap';
1590-1599: UseisUserCancelledErrorandgetUserFriendlyErrorMessagefor purchase error handlingFor
onPurchaseError(lines 1590–1599) and therequestPurchasecatch block (lines 1809–1814), you currently useerror.messagedirectly. To align with the library's error handling patterns and improve UX:
- Use
isUserCancelledError(error)to detect and handle user cancellations separately (softer messaging or silent return).- Use
getUserFriendlyErrorMessage(error)to get localized, user-friendly messages instead of raw error messages.Example pattern:
-import {ErrorCode} from 'react-native-iap'; +import {ErrorCode, isUserCancelledError, getUserFriendlyErrorMessage} from 'react-native-iap'; onPurchaseError: (error: PurchaseError) => { console.error('Subscription failed:', error); setIsProcessing(false); if (isUserCancelledError(error)) { setPurchaseResult('❌ Subscription cancelled by user'); return; } const dt = Date.now() - lastSuccessAtRef.current; if (error?.code === ErrorCode.ServiceError && dt >= 0 && dt < 1500) { return; } const message = getUserFriendlyErrorMessage(error); setPurchaseResult(`❌ Subscription failed: ${message}`); Alert.alert('Subscription Failed', message); }Apply the same pattern to the catch block at lines 1809–1814.
1425-1547: Consider platform-specific IAPKit payload structure for clearer intent and improved UX on verification failureThe verification flow is well-structured: you redact sensitive fields from logs, gate behavior on
verificationMethodRef.current, validateIAPKIT_API_KEY, and usegetErrorMessagefor robust error messaging. Two refinements align with React Native best practices:
- Platform-specific
iapkitpayloadCurrently you send both
apple.jwsandgoogle.purchaseTokenwith the samejwsOrTokenvalue. To make the payload unambiguous and reflect platform-specific intent, conditionally include only the relevant key:- const verifyRequest: VerifyPurchaseWithProviderProps = { - provider: 'iapkit', - iapkit: { - apiKey, - apple: { - jws: jwsOrToken, - }, - google: { - purchaseToken: jwsOrToken, - }, - }, - }; + const verifyRequest: VerifyPurchaseWithProviderProps = { + provider: 'iapkit', + iapkit: { + apiKey, + ...(Platform.OS === 'ios' + ? {apple: {jws: jwsOrToken}} + : {google: {purchaseToken: jwsOrToken}}), + }, + };
- Clarify UX when verification fails but purchase succeeds
Because verification happens before
finishTransactionand you always show a final"Purchase completed successfully!"alert, a failed verification currently surfaces both a "Verification Failed" alert and a generic success alert. For demo purposes this is acceptable, but consider adjusting success messaging or skipping the generic alert when verification fails, so sample behavior more closely mirrors production expectations.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
NitroIap.podspec(1 hunks)example/screens/PurchaseFlow.tsx(8 hunks)example/screens/SubscriptionFlow.tsx(9 hunks)example/src/hooks/useVerificationMethod.ts(1 hunks)example/src/utils/errorUtils.ts(1 hunks)src/index.ts(5 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/src/utils/errorUtils.tsexample/screens/PurchaseFlow.tsxsrc/index.tsexample/src/hooks/useVerificationMethod.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/src/utils/errorUtils.tsexample/screens/PurchaseFlow.tsxexample/src/hooks/useVerificationMethod.tsexample/screens/SubscriptionFlow.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/src/utils/errorUtils.tsexample/screens/PurchaseFlow.tsxsrc/index.tsexample/src/hooks/useVerificationMethod.tsexample/screens/SubscriptionFlow.tsx
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.
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
📚 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/src/utils/errorUtils.tsexample/screens/PurchaseFlow.tsxsrc/index.tsexample/screens/SubscriptionFlow.tsx
📚 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.tsexample/screens/SubscriptionFlow.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.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.tsxsrc/index.tsexample/src/hooks/useVerificationMethod.tsexample/screens/SubscriptionFlow.tsx
📚 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}} : Do not call parseErrorStringToJsonObj() in app/user code; errors are already normalized by the library.
Applied to files:
example/screens/PurchaseFlow.tsxexample/screens/SubscriptionFlow.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.tsxsrc/index.tsexample/screens/SubscriptionFlow.tsx
🧬 Code graph analysis (2)
src/index.ts (3)
src/types.ts (3)
VerifyPurchaseResultIOS(874-883)VerifyPurchaseResultAndroid(853-872)MutationField(958-963)src/utils/debug.ts (1)
RnIapConsole(18-49)src/utils/error.ts (1)
parseErrorStringToJsonObj(27-77)
example/screens/SubscriptionFlow.tsx (7)
example/src/hooks/useVerificationMethod.ts (2)
VerificationMethod(4-4)useVerificationMethod(17-97)src/index.ts (2)
verifyPurchase(1423-1423)verifyPurchaseWithProvider(1446-1472)example/src/types/env.d.ts (1)
IAPKIT_API_KEY(2-2)src/types.ts (1)
VerifyPurchaseWithProviderProps(885-888)android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt (1)
verifyPurchaseWithProvider(1123-1174)ios/HybridRnIap.swift (1)
verifyPurchaseWithProvider(352-394)example/src/utils/errorUtils.ts (1)
getErrorMessage(5-25)
🪛 RuboCop (1.81.7)
NitroIap.podspec
[convention] 46-46: Prefer to_s over string interpolation.
(Style/RedundantInterpolation)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: build-android
- GitHub Check: build-ios
🔇 Additional comments (7)
src/index.ts (2)
30-31: LGTM!Type-only imports are correctly used for the renamed types, following the coding guidelines.
1413-1423: LGTM!The
verifyPurchasealias tovalidateReceiptmaintains backward compatibility while aligning with the OpenIAP naming convention. The documentation clearly explains its purpose.example/src/hooks/useVerificationMethod.ts (1)
17-96: LGTM!Well-structured hook with proper platform-specific UI handling. The ref synchronization pattern correctly addresses stale closure issues in async callbacks. The type definitions and return interface are clean.
example/screens/PurchaseFlow.tsx (4)
32-37: LGTM!Type-only import is correctly used for
VerifyPurchaseWithProviderPropsand related types, following the coding guidelines.
192-208: LGTM!Clean UI implementation for the verification method selector with clear visual feedback.
450-557: LGTM on the verification flow structure.Good use of
verificationMethodRef.currentto capture the current verification method in the async callback, avoiding stale closure issues. Error handling correctly usesgetErrorMessageinstead ofparseErrorStringToJsonObjas per the coding guidelines for example code.
570-589: LGTM!Error handling correctly uses
ErrorCode.UserCancelledfor checking user cancellation, following the preferred pattern from coding guidelines.
Add early return for empty errors array to prevent displaying 'undefined' to users when JSON.stringify(undefined) is called. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Summary by CodeRabbit
New Features
Documentation
Chores
✏️ Tip: You can customize this high-level summary in your review settings.