Skip to content

feat(api): restruct verifyPurchase with platform-specific opts#3104

Merged
hyochan merged 10 commits intomainfrom
feat/verifyPurchase
Dec 13, 2025
Merged

feat(api): restruct verifyPurchase with platform-specific opts#3104
hyochan merged 10 commits intomainfrom
feat/verifyPurchase

Conversation

@hyochan
Copy link
Owner

@hyochan hyochan commented Dec 13, 2025

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
  • Update Nitro types for platform-specific validation params
  • Update all example apps and documentation
  • Update tests for new API structure

Summary by CodeRabbit

  • Breaking Changes

    • Purchase verification now requires platform-specific options (apple/google/horizon); the old flat sku/androidOptions is deprecated.
    • Cross-platform APIs for querying active subscriptions were removed.
  • New Features

    • Horizon (Meta Quest) verification support.
    • Google Play–backed receipt verification and provider-backed verification endpoint.
    • Plugin option to supply an IAPKit API key.
  • Chores

    • OpenIAP dependency versions bumped; spell-check dictionary updated.
  • Documentation

    • Expanded docs, examples, and migration guidance for platform-specific verification.

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

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
@coderabbitai
Copy link

coderabbitai bot commented Dec 13, 2025

Note

Other AI code review bot(s) detected

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

Walkthrough

Refactors receipt verification to platform-specific option objects (apple, google, horizon), updates native bridges to consume those shapes (including OpenIAP Google verification), removes cross-platform subscription helpers, updates types/docs/examples/tests, adds IAPKit API key plugin wiring, and bumps OpenIAP versions and editor spell-check words.

Changes

Cohort / File(s) Summary
API & Types
src/index.ts, src/hooks/useIAP.ts, src/types.ts, src/specs/RnIap.nitro.ts
validateReceipt now accepts a platform-specific VerifyPurchaseProps object (apple/google/horizon); old sku/androidOptions surface deprecated; added per-platform VerifyPurchase* types and VerifyPurchaseResultHorizon; exported verifyPurchase and verifyPurchaseWithProvider.
Native bridges
ios/HybridRnIap.swift, android/src/main/java/.../HybridRnIap.kt
iOS validateReceipt now requires apple.sku; Android validate uses OpenIAP Google verify flow (VerifyPurchaseGoogleOptions) and maps results into Nitro types; error mappings adjusted.
Removed cross-platform subscription helpers
src/index.ts, ios/HybridRnIap.swift, android/.../HybridRnIap.kt
Deleted public APIs getActiveSubscriptions and hasActiveSubscriptions and their platform implementations.
Examples & Screens
example-expo/app/purchase-flow.tsx, example-expo/app/subscription-flow.tsx, example/screens/PurchaseFlow.tsx, example/screens/SubscriptionFlow.tsx
Example calls switched from single sku to platform-aware payloads (apple, google, optional horizon); expanded in-code documentation and subscription flow helpers.
Docs & Blog
docs/blog/2025-12-11-release-14.6.0-billing-programs.md, docs/docs/getting-started/setup-ios.md, docs/docs/api/methods/core-methods.md, docs/docs/api/types.md, docs/docs/api/use-iap.md, docs/docs/guides/expo-plugin.md
Documented breaking changes and new per-platform types/usages for verifyPurchase/validateReceipt, added examples for provider-based verification and IAPKit API key guidance.
Plugin & Config
plugin/src/withIAP.ts, plugin/__tests__/*
Plugin withIAP accepts iapkitApiKey, injects Android meta-data and iOS Info.plist entry when provided; tests and test mocks extended for plist/entitlements/podfile handling.
Tests
src/__tests__/index.test.ts, plugin/__tests__/withIAP-android.test.ts, plugin/__tests__/withIAP-ios.test.ts
Tests updated to new platform-specific payload shapes; new plugin tests cover IAPKit API key behavior.
Manifests & Versions
.vscode/settings.json, openiap-versions.json, docs/blog/tags.yml
Added cSpell words (iapkit, martie); bumped OpenIAP patch versions; added blog tags for horizon/meta/verification topics.
Docs config & README
docs/docusaurus.config.ts, README.md, docs/versioned_docs/version-14.5/...
Docusaurus versioning updated, README badge added, minor header anchor additions in archived docs.

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>
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Focus areas:
    • Android OpenIAP integration and error-type/result mapping in android/src/main/java/.../HybridRnIap.kt.
    • Type consistency across src/types.ts, src/specs/RnIap.nitro.ts, and src/index.ts (VerifyPurchase* and VerifyPurchaseResult).
    • validateReceipt/verifyPurchase signature change impact on hooks, examples, and tests (src/hooks/useIAP.ts, src/__tests__/*, example apps).
    • Plugin iapkitApiKey wiring and test mocks (plist/entitlements/podfile) in plugin/src/withIAP.ts and plugin/__tests__/*.
    • Removal of getActiveSubscriptions/hasActiveSubscriptions — search for remaining callers.

Possibly related PRs

Suggested labels

🚽 migration, ❄️ types, 📖 documentation

Poem

🐰 I hopped from one SKU to three neat lanes,

apple, google, horizon — tidy brains.
Bridges hum while OpenIAP sings clear,
Docs and tests nibble carrots near.
A crunchy changelog carrot — prosperous year!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(api): restruct verifyPurchase with platform-specific opts' directly and clearly describes the main change: restructuring the verifyPurchase API to use platform-specific options instead of a flat structure, which is the primary focus of this breaking change PR.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/verifyPurchase

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

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

This pull request introduces a significant breaking change to the verifyPurchase and validateReceipt APIs, refactoring them to accept platform-specific options for Apple, Google, and Meta Horizon. This change enhances the API's flexibility and extensibility, allowing for more precise control over platform-specific validation parameters and enabling future expansion. It also rectifies a long-standing issue where Android receipt validation was returning mock data, now integrating with the actual OpenIAP verification logic.

Highlights

  • API Restructuring: The verifyPurchase and validateReceipt APIs now utilize platform-specific options (apple, google, horizon), moving the sku parameter from the root level into these platform-specific objects.
  • Android Validation Fix: The Android validateReceipt implementation has been corrected to call the actual OpenIAP verifyPurchase method, replacing the previous mock data return with real validation logic.
  • Meta Quest Support: New horizon options have been introduced to enable purchase verification for the Meta Quest (Horizon) platform, expanding the API's cross-platform capabilities.
  • API Renames and Consistency: The androidOptions parameter has been renamed to google, and productToken within the Google options is now purchaseToken, enhancing clarity and consistency across the API.
  • Documentation and Examples Updated: All relevant documentation and example applications have been thoroughly updated to reflect the new API structure and usage, ensuring developers have clear guidance.
  • Enhanced Type Safety: Nitro types have been updated to fully support the new platform-specific validation parameters, significantly improving type safety and developer experience.
Using Gemini Code Assist

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

Invoking Gemini

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

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

Customization

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

Limitations & Feedback

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

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

Footnotes

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

Copy link
Contributor

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

Choose a reason for hiding this comment

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

Code Review

This pull request 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)
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 via verifyResult.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 have parseOpenIapError() 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 .developerError into .receiptFailed for missing apple.sku.
Right now, a missing/invalid apple.sku becomes a receipt failure due to the broad catch, 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 when purchaseToken (or real accessToken) is missing.
As written, Android can hit a hard failure if purchase.purchaseToken is empty, because native validation requires google.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 for jsonRepresentation.
If renewalInfoIOS.jsonRepresentation isn’t guaranteed to be a string at runtime, consider String(...) before substring(...) 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 tokens

The comment says the library handles platform detection, but this sample hardcodes google.accessToken and may pass empty purchaseToken (purchase.purchaseToken ?? ''). In practice, Android validation requires non-empty google.* fields and developers shouldn’t need to ship placeholder secrets. Consider constructing options via Platform.OS and only including the relevant branch. As per coding guidelines, use Platform.OS checks 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-scoped verifyPurchase options over placeholders

This sample currently encourages providing both apple and google fields plus a placeholder OAuth token. Consider the same per-platform option construction to avoid implying that fake accessToken/empty purchaseToken is acceptable. As per coding guidelines, use Platform.OS checks 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 placeholder google.accessToken

Same as the other examples: this encourages a “pass everything” payload with placeholder OAuth token. Prefer a Platform.OS-scoped options object (and avoid suggesting empty purchaseToken). As per coding guidelines, use Platform.OS checks 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 horizon without requiring google.*.

Recommend tweaking the docs to show:

  1. Minimal iOS usage ({apple: {sku: ...}})
  2. Minimal Android usage ({google: {...}})
  3. 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

📥 Commits

Reviewing files that changed from the base of the PR and between 6456731 and 0fea343.

⛔ Files ignored due to path filters (1)
  • example/ios/Podfile.lock is 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.ts
  • src/__tests__/index.test.ts
  • src/specs/RnIap.nitro.ts
  • src/hooks/useIAP.ts
  • src/types.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • src/index.ts
  • example/screens/SubscriptionFlow.tsx
  • example-expo/app/subscription-flow.tsx
  • src/__tests__/index.test.ts
  • src/specs/RnIap.nitro.ts
  • example-expo/app/purchase-flow.tsx
  • example/screens/PurchaseFlow.tsx
  • src/hooks/useIAP.ts
  • src/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.ts
  • example/screens/SubscriptionFlow.tsx
  • example-expo/app/subscription-flow.tsx
  • src/__tests__/index.test.ts
  • src/specs/RnIap.nitro.ts
  • example-expo/app/purchase-flow.tsx
  • example/screens/PurchaseFlow.tsx
  • src/hooks/useIAP.ts
  • src/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.swift
  • android/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.tsx
  • example-expo/app/subscription-flow.tsx
  • example-expo/app/purchase-flow.tsx
  • example/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.ts
  • example/screens/SubscriptionFlow.tsx
  • example-expo/app/subscription-flow.tsx
  • src/__tests__/index.test.ts
  • src/specs/RnIap.nitro.ts
  • example-expo/app/purchase-flow.tsx
  • docs/blog/2025-12-11-release-14.6.0-billing-programs.md
  • example/screens/PurchaseFlow.tsx
  • src/hooks/useIAP.ts
  • src/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.ts
  • ios/HybridRnIap.swift
  • example/screens/SubscriptionFlow.tsx
  • example-expo/app/subscription-flow.tsx
  • src/__tests__/index.test.ts
  • src/specs/RnIap.nitro.ts
  • example-expo/app/purchase-flow.tsx
  • example/screens/PurchaseFlow.tsx
  • android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
  • src/hooks/useIAP.ts
  • docs/docs/getting-started/setup-ios.md
  • src/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.tsx
  • example-expo/app/subscription-flow.tsx
  • src/__tests__/index.test.ts
  • example-expo/app/purchase-flow.tsx
  • docs/blog/2025-12-11-release-14.6.0-billing-programs.md
  • example/screens/PurchaseFlow.tsx
  • src/hooks/useIAP.ts
  • docs/docs/getting-started/setup-ios.md
  • src/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.tsx
  • example-expo/app/subscription-flow.tsx
  • src/__tests__/index.test.ts
  • src/specs/RnIap.nitro.ts
  • example-expo/app/purchase-flow.tsx
  • example/screens/PurchaseFlow.tsx
  • android/src/main/java/com/margelo/nitro/iap/HybridRnIap.kt
  • src/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.tsx
  • src/__tests__/index.test.ts
  • example-expo/app/purchase-flow.tsx
  • example/screens/PurchaseFlow.tsx
  • src/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 iOS apple.sku contract.


861-866: Test update matches new Android google.* 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, and gql-v1.3.4 tags 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) to apple.sku in the validateReceipt function, 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 type syntax per coding guidelines, and the new platform-specific types align with the canonical types in src/types.ts.


70-72: LGTM!

The interface correctly maps the single sku field from VerifyPurchaseAppleOptions.


74-80: LGTM!

All fields from VerifyPurchaseGoogleOptions are correctly mapped with proper optional handling for isSub.


82-86: LGTM!

All fields from VerifyPurchaseHorizonOptions are 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.ts interface files, run yarn specs to 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, validateReceipt expects 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 accessToken and purchaseToken values. The rename from productToken to purchaseToken aligns 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 NitroReceiptValidationParams in the Nitro spec.


1182-1182: LGTM! Horizon verification result type.

The VerifyPurchaseResultHorizon interface 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)
@hyochan hyochan added the 🎯 feature New feature label Dec 13, 2025
@codecov
Copy link

codecov bot commented Dec 13, 2025

Codecov Report

❌ Patch coverage is 84.74576% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.66%. Comparing base (045933e) to head (a5255ba).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
src/index.ts 78.57% 6 Missing ⚠️
plugin/src/withIAP.ts 93.33% 2 Missing ⚠️
src/hooks/useIAP.ts 0.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            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     
Flag Coverage Δ
library 64.66% <84.74%> (+0.74%) ⬆️

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

Files with missing lines Coverage Δ
src/hooks/useIAP.ts 41.61% <0.00%> (+0.27%) ⬆️
plugin/src/withIAP.ts 59.67% <93.33%> (+5.87%) ⬆️
src/index.ts 68.42% <78.57%> (+0.33%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

- 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)
@hyochan hyochan changed the title feat(api): restructure verifyPurchase API with platform-specific options feat(api): restruct verifyPurchase API with platform-specific opts Dec 13, 2025
@hyochan hyochan changed the title feat(api): restruct verifyPurchase API with platform-specific opts feat(api): restruct verifyPurchas with platform-specific opts Dec 13, 2025
- 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
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 include purchaseToken / 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 legacy validateReceipt(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 verifyPurchase instead, use that in the example rather than the deprecated name.)


234-271: Hook “void-return” guidance vs examples: avoid await fetchProducts(...) when teaching state-driven flow.

You can keep await for sequencing, but the doc currently reads like await fetchProducts is how you “get” products. I’d switch to fetchProducts(...) and then reference subscriptions state 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 if statements.

🧹 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 iapkitApiKey in 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 any type 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 direct ErrorCode checks).

In onPurchaseError, prefer isUserCancelledError() + getUserFriendlyErrorMessage() (per repo guidelines for example/**/*.{ts,tsx}), instead of checking error.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 only dev.iapkit.API_KEY without 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/**/*, use isUserCancelledError() + getUserFriendlyErrorMessage() instead of ad-hoc suppression (e.g., ErrorCode.ServiceError timing window) and raw error.message alerts.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 13fd7c0 and 0108fa4.

📒 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.ts
  • src/hooks/useIAP.ts
  • src/index.ts
**/*.nitro.ts

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • src/specs/RnIap.nitro.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

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

Applied to files:

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

Applied to files:

  • src/specs/RnIap.nitro.ts
  • example/screens/PurchaseFlow.tsx
  • example/screens/SubscriptionFlow.tsx
  • docs/docs/getting-started/setup-ios.md
  • docs/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.ts
  • plugin/__tests__/withIAP-ios.test.ts
  • docs/docs/guides/expo-plugin.md
  • example/screens/PurchaseFlow.tsx
  • example/screens/SubscriptionFlow.tsx
  • plugin/__tests__/withIAP-android.test.ts
  • docs/docs/getting-started/setup-ios.md
  • docs/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.ts
  • example/screens/PurchaseFlow.tsx
  • src/index.ts
  • example/screens/SubscriptionFlow.tsx
  • docs/docs/getting-started/setup-ios.md
  • docs/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}. Using productId for 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: validateReceipt section 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 validateReceipt signature now properly:

  • Accepts VerifyPurchaseProps (platform-specific options)
  • Returns Promise<VerifyPurchaseResult> (not Promise<any>)
  • Includes deprecation notice directing users to verifyPurchase

This addresses the previous type regression concern.


310-314: LGTM! Implementation correctly delegates to internal handler.

The implementation properly forwards the platform-specific VerifyPurchaseProps to validateReceiptInternal, 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_KEY meta-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 IAPKitAPIKey in 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 iapkitApiKey to Android plugin (line 455)
  • Conditionally applies iOS Info.plist key (lines 462-464)
  • Properly documents the feature in IapPluginProps type
  • 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 sku moved into platform-specific options
  • Documents androidOptionsgoogle rename
  • Documents productTokenpurchaseToken rename
  • Shows new horizon platform 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 VerifyPurchaseProps structure
  • Matches the type definitions in src/types.ts
src/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 | null for Nitro compatibility with optional fields

This ensures the Nitro bridge stays in sync with the TypeScript API surface.


838-846: LGTM! Deprecation notice properly added to Nitro spec.

The validateReceipt method:

  • 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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 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 Types or 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

📥 Commits

Reviewing files that changed from the base of the PR and between 0108fa4 and 145c8d1.

📒 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:

  • sku relocation to platform-specific options
  • androidOptionsgoogle rename
  • productTokenpurchaseToken rename
  • New horizon option for Meta Quest

The 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.

@hyochan hyochan changed the title feat(api): restruct verifyPurchas with platform-specific opts feat(api): restruct verifyPurchase with platform-specific opts Dec 13, 2025
- 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)
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
example/screens/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

📥 Commits

Reviewing files that changed from the base of the PR and between 145c8d1 and a5255ba.

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

Applied to files:

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

Applied to files:

  • example/screens/PurchaseFlow.tsx
  • src/index.ts
📚 Learning: 2025-10-02T19:35:19.667Z
Learnt from: CR
Repo: hyochan/react-native-iap PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-10-02T19:35:19.667Z
Learning: Applies to {src/**/*.{ts,tsx},example/**/*.{ts,tsx},example-expo/**/*.{ts,tsx}} : 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: null when 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 verifyPurchase alias (line 1489) provides a consistent API surface, and verifyPurchaseWithProvider (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.

@hyochan hyochan merged commit 9721c21 into main Dec 13, 2025
12 checks passed
@hyochan hyochan deleted the feat/verifyPurchase branch December 13, 2025 17:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🎯 feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant