diff --git a/.github/pr-previews/pr-281-setup-readability-expo.jpg b/.github/pr-previews/pr-281-setup-readability-expo.jpg
new file mode 100644
index 000000000..a4248775c
Binary files /dev/null and b/.github/pr-previews/pr-281-setup-readability-expo.jpg differ
diff --git a/.github/pr-previews/pr-281-setup-readability-flutter.jpg b/.github/pr-previews/pr-281-setup-readability-flutter.jpg
new file mode 100644
index 000000000..9a59f8949
Binary files /dev/null and b/.github/pr-previews/pr-281-setup-readability-flutter.jpg differ
diff --git a/.github/pr-previews/pr-281-setup-readability-react-native.jpg b/.github/pr-previews/pr-281-setup-readability-react-native.jpg
new file mode 100644
index 000000000..a1bae2ea9
Binary files /dev/null and b/.github/pr-previews/pr-281-setup-readability-react-native.jpg differ
diff --git a/packages/docs/src/components/Callout.tsx b/packages/docs/src/components/Callout.tsx
new file mode 100644
index 000000000..4ef2b725a
--- /dev/null
+++ b/packages/docs/src/components/Callout.tsx
@@ -0,0 +1,27 @@
+import type { ReactNode } from 'react';
+
+type CalloutKind = 'note' | 'tip' | 'important' | 'warning';
+
+const KIND_LABELS: Record
Setting up in-app purchases for Android requires configuration in Google
- Play Console and your Android project.
+ Play Console and your Android project. Building for another
+ Android-compatible store? See{' '}
+ Store Setup for Horizon OS, Fire OS,
+ Vega OS, and alternative marketplace targets.
These libraries implement the OpenIAP specification and handle
- Android-specific requirements.
+ Android-specific requirements â refer to each library's
+ documentation for implementation details.
Android requires acknowledging purchases within 3 days. Unacknowledged
- purchases are automatically refunded by Google Play.
+ purchases are automatically refunded by Google Play. In OpenIAP SDKs
+ the completion order is: verify with a trusted verifier (your backend
+ or IAPKit), grant and persist the
+ entitlement so it survives a restart, then acknowledge through{' '}
+
+
Consumable products must be consumed before they can be purchased
again. This prevents duplicate purchases of items like coins or lives.
+ Consumption is the{' '}
+
+
Always verify purchases through a trusted verifier that calls the
- Google Play Developer API, either your backend or IAPKit, to prevent
- fraud and ensure purchase validity.
+ Google Play Developer API â either your backend or{' '}
+ IAPKit â to prevent fraud and ensure
+ purchase validity. See the{' '}
+ Purchase Verification guide.
+ OpenIAP libraries handle these Android-specific requirements
+ automatically. Consult the library documentation for your chosen
+ framework to ensure proper implementation.
+ Android Setup Guide
Prerequisites
@@ -76,19 +64,11 @@ function AndroidSetup() {
-
adb shell pm clear com.android.vending
- finishTransaction
+
+ .
@@ -424,6 +390,11 @@ dependencies {
finishTransaction
+ {' '}
+ call with isConsumable: true.
@@ -434,8 +405,10 @@ dependencies {
@@ -516,20 +489,11 @@ dependencies {
- â ī¸ Deprecated in Google Play Billing Library 8.2.0+.{' '}
- Direct acknowledge / consume calls are being phased out â use the
- cross-platform{' '}
-
- finishTransaction
- {' '}
- API instead, which handles acknowledgment automatically and is the
- recommended path for new code.
-
finishTransaction
+ {' '}
+ API instead, which handles acknowledgment automatically and is the
+ recommended path for new code.
+
- â ī¸ Deprecated in Google Play Billing Library 8.2.0+.{' '}
- Use{' '}
-
- finishTransaction
- {' '}
- with isConsumable: true instead â the unified path
- consumes (or acknowledges) the purchase automatically and stays
- forward-compatible with the new Billing Programs API.
-
finishTransaction
+ {' '}
+ with isConsumable: true instead â the unified path consumes
+ (or acknowledges) the purchase automatically and stays
+ forward-compatible with the new Billing Programs API.
+
- External Offer ordering: check availability, create
- fresh reporting details immediately before the redirect, then call{' '}
-
- launchExternalLinkAndroid()
-
- . After checkout, send that invocation's token to your backend
- and report the transaction to Google within 24 hours. Never reuse a
- token from an earlier launch attempt. See the complete{' '}
- External Offer flow
- .
-
launchExternalLinkAndroid()
+
+ . After checkout, send that invocation's token to your backend and
+ report the transaction to Google within 24 hours. Never reuse a token
+ from an earlier launch attempt. See the complete{' '}
+ External Offer flow.
+ request* APIs
- âšī¸{' '}
This note is about sibling APIs, not fetchProducts.{' '}
fetchProducts itself is a regular promise-based call â
its Promise<FetchProductsResult> return value{' '}
@@ -87,7 +87,7 @@ function FetchProducts() {
.
- Critical: Android purchases must be acknowledged - within 3 days or they will be automatically refunded. iOS transactions - will replay on every app launch if not finished. -
-
- macOS: Not supported. The current OpenIAP Apple core
- implementation uses UIApplication and returns a
- feature-not-supported error on macOS.
-
UIApplication and returns a feature-not-supported error on
+ macOS.
+
- â ī¸ Important: APIs starting with request{' '}
- are event-based operations, not promise-based.
+ APIs starting with request are event-based operations,
+ not promise-based.
While these APIs return values for various purposes, you should{' '}
@@ -82,7 +83,7 @@ function RequestPurchase() {
The request prefix indicates that these are event
requests â use the appropriate listeners to handle the actual results.
- Important: requestPurchase is event-based, not
- promise-based. Listen for the result via{' '}
-
- purchaseUpdatedListener
- {' '}
- /{' '}
-
- purchaseErrorListener
-
- .
-
purchaseUpdatedListener
+ {' '}
+ /{' '}
+
+ purchaseErrorListener
+
+ .
+
See:{' '}
diff --git a/packages/docs/src/pages/docs/ecosystem.tsx b/packages/docs/src/pages/docs/ecosystem.tsx
index 74a788829..61e5f56f5 100644
--- a/packages/docs/src/pages/docs/ecosystem.tsx
+++ b/packages/docs/src/pages/docs/ecosystem.tsx
@@ -1,4 +1,5 @@
import { useState } from 'react';
+import Callout from '../../components/Callout';
import SEO from '../../components/SEO';
import { useScrollToHash } from '../../hooks/useScrollToHash';
@@ -199,22 +200,12 @@ function Ecosystem() {
-
- Maintaining open source libraries requires significant time and
- effort. If you find OpenIAP helpful, please consider{' '}
- sponsoring to help us sustain and grow this
- ecosystem.
-
- Note: In StoreKit 2, promoted products are purchased
- through the standard{' '}
-
- requestPurchase()
- {' '}
- flow after the app receives or restores the promoted product.
-
requestPurchase()
+ {' '}
+ flow after the app receives or restores the promoted product.
+
- Critical Limitation: On Android, the{' '}
- currentPlanId and basePlanIdAndroid fields
- may return incorrect values for subscription groups with multiple
- base plans.
-
currentPlanId and{' '}
+ basePlanIdAndroid fields may return incorrect values for
+ subscription groups with multiple base plans.
+ @@ -108,15 +106,12 @@ OpenIapLog.enable(false)`} object.
-- Warning log you may see: -
+
Multiple offers (3) found for premium_subscription, using first
basePlanId (may be inaccurate)
-
- Note: This is a fundamental limitation of Google
- Play Billing API, not a bug in this library. The{' '}
-
- Purchase
- {' '}
- object from Google simply does not include basePlanId{' '}
- information.
-
Purchase
+ {' '}
+ object from Google simply does not include basePlanId{' '}
+ information.
+ - Platform Support: This feature is currently - Android-only. iOS App Store handles discounts differently through - promotional offers and introductory prices for subscriptions. -
-
- Standardized API: Read{' '}
- ProductAndroid.discountOffers. Each{' '}
- DiscountOffer exposes
- common price fields and Android-only details through the{' '}
- Android suffix (for example,{' '}
- offerTokenAndroid).
-
ProductAndroid.discountOffers. Each{' '}
+ DiscountOffer exposes
+ common price fields and Android-only details through the{' '}
+ Android suffix (for example, offerTokenAndroid
+ ).
+ - Note: Discount features require Google Play Billing - Library 8.0+. Make sure your app uses a compatible version of the - OpenIAP library. -
-
- âšī¸ Important: External purchase bypasses native
- platform billing. You must implement your own payment processing,
- verification, and entitlement systems. Platform-specific callbacks
- (like onPurchaseUpdated) will NOT fire for external
- purchases.
-
onPurchaseUpdated) will
+ NOT fire for external purchases.
+
- â ī¸ Token Reporting Requirement: When a user
- completes a purchase through developer billing, you{' '}
- must report the externalTransactionToken to
- Google Play within 24 hours. Failure to report may result in
- account suspension. Use the Google Play Developer API's{' '}
- externaltransactions.createexternaltransaction{' '}
- endpoint to report the token.
-
externaltransactions.createexternaltransaction{' '}
+ endpoint to report the token.
+ - âšī¸ Note: The iOS 18.2+ flow with dedicated - APIs provides better user experience with Apple's official - notice sheet. The entire flow happens within the app - no - browser redirect or deep linking required. -
-- â ī¸ Critical: You must complete all steps in order. - Skipping verification or failing to finish transactions will cause - issues: + You must complete all steps in order. Skipping verification or + failing to finish transactions will cause issues:
- Terminology: APIs starting with{' '}
- request are event-based operations,
- not promise-based. Do not rely on their return values for actual
- purchase results â instead, listen for events through{' '}
-
- purchaseUpdatedListener
- {' '}
- or{' '}
-
- purchaseErrorListener
-
- . See{' '}
-
- API Terminology
- {' '}
- for details.
-
request are{' '}
+ event-based operations, not promise-based. Do not
+ rely on their return values for actual purchase results â instead,
+ listen for events through{' '}
+
+ purchaseUpdatedListener
+ {' '}
+ or{' '}
+
+ purchaseErrorListener
+
+ . See{' '}
+
+ API Terminology
+ {' '}
+ for details.
+ - âšī¸ Server-Side Implementation: For detailed - server-side verification implementation (JWS verification for iOS, - Google Play API for Android), see the{' '} - Verify Purchase tutorials. -
-- â ī¸ Security Best Practices: -
+verifyPurchaseWithProvider with the{' '}
'iapkit' provider and pass the
- platform-specific token or receipt payload. Fire OS and Vega OS use
+ platform-specific token or receipt payload. Fire OS and Vega OS use{' '}
iapkit.amazon with the Amazon receipt id, and no
app-owned Amazon RVS server is required. If your own backend serves
protected paid resources, have that backend authenticate the user and
@@ -817,24 +809,22 @@ async TaskisValid alone is not enough.
-
- âšī¸ Get a project key: Sign up at{' '}
-
- kit.openiap.dev
- {' '}
- to obtain an openiap-kit_pk_ publishable key. You can
- pass it directly, or configure it once in your app (Expo extra,
- Info.plist, AndroidManifest, etc.) so the SDK picks it up
- automatically. Never use an openiap-kit_sk_ secret key
- in app code.
-
openiap-kit_pk_ publishable key. You can
+ pass it directly, or configure it once in your app (Expo extra,
+ Info.plist, AndroidManifest, etc.) so the SDK picks it up
+ automatically. Never use an openiap-kit_sk_ secret key in
+ app code.
+
- âšī¸ Endpoint: Requests are sent to{' '}
- https://kit.openiap.dev/v1/purchase/verify with{' '}
- Authorization: Bearer <apiKey>. See the{' '}
-
- PurchaseVerificationProvider
- {' '}
- type reference for the full response shape.
-
https://kit.openiap.dev/v1/purchase/verify with{' '}
+ Authorization: Bearer <apiKey>. See the{' '}
+
+ PurchaseVerificationProvider
+ {' '}
+ type reference for the full response shape.
+ - Critical: If you don't acknowledge an - Android purchase within 3 days, Google will auto-refund the - user. See Purchase{' '} - for the acknowledgment flow. -
-
- Availability: billing plan selection requires iOS,
- iPadOS, macOS, tvOS, or visionOS 26.4+ and an app compiled with the
- StoreKit billing-plan APIs available in Xcode 26.5+ / Swift 6.3+. If
- the app runs on an older Apple OS version, omit{' '}
- billingPlanType and let StoreKit purchase the default
- plan. Never send unknown as a purchase option.
-
- Treat pricingTermsIOS as the source of truth before
- showing a commitment option. If StoreKit does not return a term for
- the user's selected billingPlanType, fall back to
- the default subscription purchase and leave{' '}
- billingPlanType unset.
-
billingPlanType and let StoreKit
+ purchase the default plan. Never send unknown as a
+ purchase option.
+ pricingTermsIOS as the source of truth before
+ showing a commitment option. If StoreKit does not return a term for
+ the user's selected billingPlanType, fall back to
+ the default subscription purchase and leave{' '}
+ billingPlanType unset.
+
- âšī¸ Tip: Always fetch products first; offers only
- exist after {"fetchProducts({ type: 'subs' })"}.
-
{"fetchProducts({ type: 'subs' })"}.
+ subscriptionOffers entry.
-
- â ī¸ Required: Android subscriptions must
- include subscriptionOffers in the purchase
- request. Without it, the purchase will fail with:
+ Android subscriptions must include{' '}
+ subscriptionOffers in the purchase request.
+ Without it, the purchase will fail with:
The number of skus (1) must match: the number of offerTokens
(0)
-
- â ī¸ Google Play Billing API Limitation: The{' '}
-
- Purchase object
- {' '}
- returned by Google Play Billing does NOT{' '}
- include basePlanIdAndroid. This means{' '}
-
- getActiveSubscriptions()
- {' '}
- and purchase callbacks cannot reliably determine which
- specific plan was purchased within a subscription group. See{' '}
-
- detailed limitation and solutions
-
- .
-
basePlanIdAndroid. This means{' '}
+
+ getActiveSubscriptions()
+ {' '}
+ and purchase callbacks cannot reliably determine which
+ specific plan was purchased within a subscription group. See{' '}
+
+ detailed limitation and solutions
+
+ .
+ Important distinction: @@ -2109,26 +2105,21 @@ func _on_purchase_success(purchase: PurchaseAndroid) -> void: in your own backend model.
-- đĄ Tip: If you want to simplify server-side - verification, consider using{' '} - - IAPKit - {' '} - which handles all the complexity for you. Use{' '} - - verifyPurchaseWithProvider - {' '} - to verify purchases with minimal setup. -
-- âšī¸ Related Resources: -
-- â ī¸ Important: When a user requests and receives a - refund: -
-When a user requests and receives a refund:
++
Without server validation: App grants access â
(incorrect - refunded!)
With server validation: Server detects refund â
denies access â
- â ī¸ Android Limitation: While{' '}
+ While{' '}
getAvailablePurchases()
{' '}
@@ -2753,7 +2741,7 @@ func purchase_with_offer(subscription_id: String, offer_type: int) -> void:
time, cancellation status, or refund information. For complete
subscription management, consider:
- â ī¸ Important: Android's Play Billing - Library does not expose subscription status details (expiry, - cancellation, refund) to the client. You must use - server-side validation. -
-On Android, client-side checks are limited to verifying if a diff --git a/packages/docs/src/pages/docs/features/subscription/upgrade-downgrade.tsx b/packages/docs/src/pages/docs/features/subscription/upgrade-downgrade.tsx index 10026d8f1..05794e1cb 100644 --- a/packages/docs/src/pages/docs/features/subscription/upgrade-downgrade.tsx +++ b/packages/docs/src/pages/docs/features/subscription/upgrade-downgrade.tsx @@ -1,6 +1,7 @@ import { Link } from 'react-router-dom'; import AnchorLink from '../../../../components/AnchorLink'; import Accordion from '../../../../components/Accordion'; +import Callout from '../../../../components/Callout'; import CodeBlock from '../../../../components/CodeBlock'; import LanguageTabs from '../../../../components/LanguageTabs'; import PlatformTabs from '../../../../components/PlatformTabs'; @@ -157,29 +158,24 @@ function SubscriptionUpgradeDowngrade() {
Key insight from Apple Developer Forums:
-- "If the+autoRenewPreferencevalue is different - from theproductIDfrom{' '} -currentEntitlements, then you know that the - user already changed the subscription plan." -
-
â Apple Developer Forums:{' '} - - How to know when user upgrades/downgrades - -
+ "If the+autoRenewPreferencevalue is + different from theproductIDfrom{' '} +currentEntitlements, then you know that the + user already changed the subscription plan." +
+ â Apple Developer Forums:{' '} + + How to know when user upgrades/downgrades + +
+- Security: Never rely only on local client purchase - state. Use your backend or IAPKit as the verifier. -
-
- Public, non-authoritative data: The payload is
- client-visible and limited to 16 KiB of UTF-8. Never store secrets
- or server-only authorization rules in it, and never use its content
- instead of isValid, state, or the
- store-verified productId for entitlement decisions.
+ The payload is client-visible and limited to 16 KiB of UTF-8. Never
+ store secrets or server-only authorization rules in it, and never
+ use its content instead of isValid, state,
+ or the store-verified productId for entitlement
+ decisions.
A project key compiled into an app can be extracted. It keeps its @@ -514,7 +513,7 @@ if ( project's quota; create separate keys for each build or environment and rotate or revoke a key when a build is retired or compromised.
-This is request/response retrieval, not an APNs or FCM push. To fetch rules when the app opens without verifying a new purchase, use the @@ -544,10 +543,7 @@ if ( Error Handling Best Practice
- Important: Verification error â Invalid purchase -
+When verification throws an error, it does NOT mean the purchase is invalid. Errors can occur due to network issues, server downtime, or @@ -561,7 +557,7 @@ if ( At the same time, never grant or finish a new purchase that has not been verified.
-diff --git a/packages/docs/src/pages/docs/foundation/founding-supporters.tsx b/packages/docs/src/pages/docs/foundation/founding-supporters.tsx index c870bcd18..f654d0e15 100644 --- a/packages/docs/src/pages/docs/foundation/founding-supporters.tsx +++ b/packages/docs/src/pages/docs/foundation/founding-supporters.tsx @@ -1,5 +1,6 @@ import SEO from '../../../components/SEO'; import AnchorLink from '../../../components/AnchorLink'; +import Callout from '../../../components/Callout'; import { useScrollToHash } from '../../../hooks/useScrollToHash'; import { Link } from 'react-router-dom'; @@ -15,21 +16,10 @@ function FoundingSupporters() { keywords="OpenIAP founding supporter, open source sponsor, IAP standard supporter" />
We're building OpenIAP into a vendor-neutral, open standard for in-app purchases â and we're looking for organizations to join as Founding diff --git a/packages/docs/src/pages/docs/foundation/governance.tsx b/packages/docs/src/pages/docs/foundation/governance.tsx index fbe206492..87c307464 100644 --- a/packages/docs/src/pages/docs/foundation/governance.tsx +++ b/packages/docs/src/pages/docs/foundation/governance.tsx @@ -1,5 +1,6 @@ import SEO from '../../../components/SEO'; import AnchorLink from '../../../components/AnchorLink'; +import Callout from '../../../components/Callout'; import { useScrollToHash } from '../../../hooks/useScrollToHash'; function Governance() { @@ -14,21 +15,10 @@ function Governance() { keywords="OpenIAP governance, open source governance, TSC, technical steering committee, maintainer policy" />
OpenIAP is an open-source project providing a neutral interoperability standard for in-app purchase APIs and verification across platforms. diff --git a/packages/docs/src/pages/docs/foundation/one-pager.tsx b/packages/docs/src/pages/docs/foundation/one-pager.tsx index 8023c495d..634541ffb 100644 --- a/packages/docs/src/pages/docs/foundation/one-pager.tsx +++ b/packages/docs/src/pages/docs/foundation/one-pager.tsx @@ -1,5 +1,6 @@ import SEO from '../../../components/SEO'; import AnchorLink from '../../../components/AnchorLink'; +import Callout from '../../../components/Callout'; import { useScrollToHash } from '../../../hooks/useScrollToHash'; function OnePager() { @@ -17,21 +18,10 @@ function OnePager() { OpenIAP: Neutral Interoperability Layer for In-App Purchase APIs and Verification -
This document outlines how OpenIAP plans to grow and how sponsorship funding is allocated. Full transparency on where every dollar goes. diff --git a/packages/docs/src/pages/docs/foundation/sponsorship.tsx b/packages/docs/src/pages/docs/foundation/sponsorship.tsx index 7e95b8879..c8447a181 100644 --- a/packages/docs/src/pages/docs/foundation/sponsorship.tsx +++ b/packages/docs/src/pages/docs/foundation/sponsorship.tsx @@ -1,5 +1,6 @@ import SEO from '../../../components/SEO'; import AnchorLink from '../../../components/AnchorLink'; +import Callout from '../../../components/Callout'; import { useScrollToHash } from '../../../hooks/useScrollToHash'; import { Link } from 'react-router-dom'; @@ -15,21 +16,10 @@ function Sponsorship() { keywords="OpenIAP sponsorship, open source funding, IAP sponsor, founding supporter" />
OpenIAP is the open interoperability standard for in-app purchases. Your sponsorship directly funds the infrastructure, security, and diff --git a/packages/docs/src/pages/docs/guides/ai-assistants.tsx b/packages/docs/src/pages/docs/guides/ai-assistants.tsx index 56cf4dccc..e54d8a5bd 100644 --- a/packages/docs/src/pages/docs/guides/ai-assistants.tsx +++ b/packages/docs/src/pages/docs/guides/ai-assistants.tsx @@ -1,4 +1,5 @@ import AnchorLink from '../../../components/AnchorLink'; +import Callout from '../../../components/Callout'; import CodeBlock from '../../../components/CodeBlock'; import SEO from '../../../components/SEO'; import TLDRBox from '../../../components/TLDRBox'; @@ -283,35 +284,25 @@ getAvailablePurchases in OpenIAP?"`}
- Tip: For complex implementations, start with{' '} - - llms.txt - {' '} - for quick answers, then reference{' '} - - llms-full.txt - {' '} - when you need detailed type information or platform-specific APIs. -
-finishTransaction is the most common cause of purchase
- issues. Always finish transactions after delivering content, even if
- verification fails.
- finishTransaction is the most common
+ cause of purchase issues. Finish every transaction once verification
+ and delivery succeed â unfinished transactions replay on iOS and are
+ auto-refunded on Android. If verification fails with a transient
+ error, leave the transaction unfinished so it is redelivered and can
+ be retried.
+ + Use Xcode 16.4 or later. Compile with Xcode 27 when you need the + StoreKit 27 fields documented by OpenIAP 3, and complete the UIScene + migration below before shipping that build. +
These libraries implement the OpenIAP specification and handle - iOS-specific requirements. + iOS-specific requirements â refer to each library's documentation + for implementation details.
- -iOS requires purchase verification to validate purchases. StoreKit 2 (iOS 15+) provides JWS (JSON Web Signature) for enhanced security, - while older versions use base64-encoded receipts. + while older versions use base64-encoded receipts. See the{' '} + + Purchase Verification guide + {' '} + for server-side and IAPKit verification flows.
iOS requires apps to provide a "Restore Purchases" button for users to
recover their non-consumable purchases and active subscriptions on new
- devices.
+ devices â wire it to{' '}
+
+ restorePurchases
+ {' '}
+ and read results with{' '}
+
+ getAvailablePurchases
+
+ .
+ OpenIAP libraries handle these iOS-specific requirements + automatically. Consult the library documentation for your chosen + framework to ensure proper implementation. +
A 304 still makes one Convex query invocation.{' '} Convex returns a time-independent row snapshot and invalidates its @@ -738,7 +739,7 @@ async function refreshEntitlements( purchase tokens in general-purpose local storage when product IDs, states, and expiry times are sufficient.
-Contact us before a high-volume production launch.{' '} If your organization expects to consume a meaningful share of hosted @@ -819,7 +820,7 @@ async function refreshEntitlements( . For capacity planning or a separate written arrangement, contact{' '} hyo@hyo.dev.
-Client payloads are public app data. Anyone able to call the project's client endpoints may receive them. Never store @@ -891,7 +892,7 @@ async function refreshEntitlements( independent builds or environments and never put a secret key in the app.
-- âšī¸ Tip: Use{' '} - - verifyPurchase - {' '} - for server-side purchase verification. -
-
- expo-iap provides in-app purchase support for Expo apps.
- Works with both managed and bare workflows.
+ expo-iap provides in-app purchase support for Expo apps â
+ both the managed workflow and bare apps that prefer the Expo Modules
+ stack. For bare React Native we recommend{' '}
+ react-native-iap (same API);
+ see React Native CLI Projects for using expo-iap
+ in bare apps.
kotlinVersion explicitly with expo-build-properties as
+ shown below.
npx expo prebuild).
-
- Xcode 27 builds must use the UIScene lifecycle. Regenerate with an
- Expo template that creates React Native from{' '}
- ExpoAppSceneDelegate, or migrate an older generated iOS
- host before building. Confirm that Info.plist contains a
- scene configuration and that AppDelegate.swift no longer
- creates UIWindow(frame: UIScreen.main.bounds). See the{' '}
+
Info.plist needs a scene configuration,
+ and AppDelegate.swift must no longer create{' '}
+ UIWindow(frame: UIScreen.main.bounds). Newer Expo
+ templates (based on ExpoAppSceneDelegate) generate this
+ correctly.
+
The expo-iap config plugin supports these options:
++ The expo-iap config plugin does two things: it wires your IAPKit + publishable key into the app for hosted{' '} + purchase verification, and it + enables optional store modules â{' '} + Onside (an iOS alternative + marketplace), Horizon OS{' '} + (Meta Quest), and Amazon{' '} + (Fire OS devices and the Vega OS runtime). All modules are off by + default; enable only the stores you ship to. +
- Use this page for the Expo plugin shape. Store-specific values, + Use this page for the Expo plugin shape. Store-specific values â required developer-console fields, supported targets, and artifact - rules live in Store Setup: + rules â live in each store's setup page linked above.
-
- Keep module enable flags under modules and
- platform-specific values under android or{' '}
- ios. For Amazon targets, use{' '}
- modules.amazon.fireOS and{' '}
- modules.amazon.vegaOS; use{' '}
- android.amazon.vegaOS only when Vega metadata must differ
- from the normal Expo app config.
+ Module enable flags live under modules; platform-specific
+ values live under android or ios. For
+ Amazon, modules.amazon.fireOS and{' '}
+ modules.amazon.vegaOS toggle each target; the separate{' '}
+ android.amazon.vegaOS block is only needed when your Vega
+ OS build requires different values (app id, artifacts) than your
+ regular Android config â see{' '}
+ Amazon Store Setup.
+ Under the hood, the typical flow is{' '}
+
+ initConnection
+ {' '}
+ â set up{' '}
+
+ purchaseUpdatedListener
+ {' '}
+ and{' '}
+
+ purchaseErrorListener
+ {' '}
+ â{' '}
+
+ fetchProducts
+ {' '}
+ â{' '}
+
+ requestPurchase
+ {' '}
+ â{' '}
+
+ finishTransaction
+
+ , with{' '}
+
+ endConnection
+ {' '}
+ on teardown. The useIAP hook manages the connection and
+ listener steps for you. See the{' '}
+ Purchase Guide for the
+ complete flow.
+
finishTransaction{' '}
- after verifying a purchase. On Android, unfinished purchases are
- automatically refunded after 3 days.
- useIAP hook API is
- identical to react-native-iap. Methods return{' '}
- Promise<void> and update internal state. Use{' '}
- onPurchaseSuccess for purchase results.
-
+ Each call here has a full reference â see{' '}
+
+ fetchProducts
+
+ ,{' '}
+
+ requestPurchase
+
+ , and{' '}
+
+ finishTransaction
+ {' '}
+ for parameters and per-store behavior, and{' '}
+
+ ErrorCode
+ {' '}
+ for the full error reference.
+
finishTransaction after verifying a purchase.
+ On Android, unfinished purchases are automatically refunded after 3
+ days.
+
+ The useIAP hook API is identical to react-native-iap:
+ methods return Promise<void> and update internal
+ state â use onPurchaseSuccess for purchase results.
+
+ expo-iap and react-native-iap share the same OpenIAP API; they differ + only in tooling: +
npx expo install instead of{' '}
npm install
Replace react-native with react-native-tvos{' '}
in your package.json:
@@ -620,19 +592,11 @@ EXPO_TV=1 npx expo prebuild --platform ios --clean
EXPO_TV=1 npx expo run:ios --device "Apple TV 4K (3rd generation)"`}
-
presentCodeRedemptionSheetIOS is{' '}
+ presentCodeRedemptionSheetIOS is{' '}
not supported on tvOS. Direct users to redeem codes
on their iPhone or through Apple TV settings instead.
- If you cannot upgrade, create a custom config plugin to force an older @@ -697,7 +653,12 @@ module.exports = function withBillingLibraryDowngrade(config) { -
minSdkVersion {ANDROID_SDK.minSdk}+ (see the platform
+ sections below)
+
- Add the following to your ios/Runner/Info.plist (iOS
- 14+):
+ Declaring itms-apps in ios/Runner/Info.plist{' '}
+ is only needed when your own code checks App Store links before
+ opening them (for example canLaunchUrl from url_launcher)
+ â the plugin itself does not require it:
missingDimensionStrategy{' '}
- configuration is required since v7.1.14 due to product flavor support
- for Meta Horizon OS and Fire OS. Keep this page focused on Flutter
- installation; use Store Setup for target-specific Android flavor
- details.
- missingDimensionStrategy line is required since
+ v7.1.14 because the Android library ships product flavors for
+ alternative stores â Meta Horizon OS (Quest headsets) and Amazon Fire
+ OS. Apps shipping to Google Play always select the play{' '}
+ flavor shown above. Targeting Meta Quest or Amazon devices instead?
+ See Horizon Store Setup or{' '}
+ Amazon Store Setup for the
+ flavor to select there.
+ @@ -207,25 +226,35 @@ function FlutterSetup() { # - -
dataAndroid. Flutter 10 does not accept the former
- custom-channel alias. Native adapters, MethodChannel fixtures, and
- mocks must emit dataAndroid. See{' '}
-
- Deprecations & 3.0 Migration
-
- .
-
+ The typical flow is{' '}
+
+ initConnection
+ {' '}
+ â set up{' '}
+
+ purchaseUpdatedListener
+ {' '}
+ and{' '}
+
+ purchaseErrorListener
+ {' '}
+ â{' '}
+
+ fetchProducts
+ {' '}
+ â{' '}
+
+ requestPurchase
+ {' '}
+ â{' '}
+
+ finishTransaction
+
+ . The snippets below cover each step; the{' '}
+ Purchase Guide shows the full
+ flow with receipt validation.
+
+ Each call here has a full reference â see{' '}
+
+ initConnection
+
+ ,{' '}
+
+ purchaseUpdatedListener
+
+ ,{' '}
+
+ purchaseErrorListener
+
+ ,{' '}
+
+ finishTransaction
+
+ , and{' '}
+
+ endConnection
+ {' '}
+ for parameters and per-store behavior, and{' '}
+ Error Codes for the ErrorCode{' '}
+ values.
+
finishTransaction{' '}
- after verifying a purchase. On Android, unfinished purchases are
- automatically refunded after 3 days.
- finishTransaction after verifying a purchase.
+ On Android, unfinished purchases are automatically refunded after 3
+ days.
+
+ See{' '}
+
+ fetchProducts
+ {' '}
+ for parameters and per-store behavior.
+
+
+ requestPurchase
+ {' '}
+ does not return the purchase. Results arrive on the{' '}
+
+ purchaseUpdatedListener
+ {' '}
+ stream you set up in Basic Setup (unlike
+ the callback-style hooks in the React Native SDKs), so make sure both
+ listeners are active before you call it.
+
purchaseUpdatedListener and{' '}
- purchaseErrorListener listeners before calling{' '}
- requestPurchase.
-
+ See{' '}
+
+ getAvailablePurchases
+ {' '}
+ for parameters and per-store behavior.
+
PurchaseState.Pending
+
+
+ The public Purchase field is{' '}
+ dataAndroid. Flutter 10 does not accept the former
+ custom-channel alias. Native adapters, MethodChannel fixtures, and
+ mocks must emit dataAndroid. See{' '}
+
+ Deprecations & 3.0 Migration
+
+ .
+
The zip includes pre-built binaries for both iOS and Android.
-
- Native Apple API availability is fixed when the pre-built{' '}
- GodotIap.framework is compiled. In particular, the
- verified Apple 27 offer-code result requires a framework built with
- Xcode 27 or later; a framework built with Xcode 26 still presents the
- legacy sheet and returns null, even when the app runs on
- Apple 27. The published godot-iap 3.0.0 iOS framework is built with
- Xcode 27 and its release workflow rejects an older artifact. Custom
- builds must use Xcode 27 to retain that result path.
-
- Release zips are intended for iOS export and Android. If you use a
- release or custom build that includes{' '}
- addons/godot-iap/bin/macos, and Godot reports that{' '}
- GodotIap.framework or{' '}
- SwiftGodotRuntime.framework is damaged on macOS, clear
- quarantine and repair the local ad-hoc signature:
-
- The checked-in macOS runtime frameworks are Apple Silicon (
- arm64) only. Custom source builds can override{' '}
- MACOS_ARCHS; make macos requests{' '}
- arm64 x86_64 by default, and generated metadata should
- only include architectures that the framework binaries actually
- contain. The default release zip does not include macOS runtime
- frameworks.
-
+ The checked-in macOS runtime frameworks are Apple Silicon (
+ arm64) only. Custom source builds can override{' '}
+ MACOS_ARCHS; make macos requests{' '}
+ arm64 x86_64 by default, and generated metadata should
+ only include architectures that the framework binaries actually
+ contain.
+
+ The pre-built iOS framework locks in Apple API availability at compile
+ time. One feature depends on this:{' '}
+
+ offer code redemption
+ {' '}
+ only returns a verified result when the framework was built with Xcode
+ 27 or later â a framework built with Xcode 26 falls back to the legacy
+ redemption sheet and returns null, even on devices
+ running the latest OS. The published godot-iap 3.0.0 framework is
+ built with Xcode 27. If you build from source and use offer codes,
+ build with Xcode 27 or later.
+
+ The default release zip does not include macOS runtime frameworks, so
+ most projects can skip this section. It applies only if you build from
+ source with macOS support or use a custom zip containing{' '}
+ addons/godot-iap/bin/macos. If Godot reports that{' '}
+ GodotIap.framework or{' '}
+ SwiftGodotRuntime.framework is damaged, clear quarantine
+ and repair the ad-hoc signature:
+
Library not loaded: @rpath/GodotIap.framework/GodotIap.
If the runpath is missing, it can crash with:{' '}
@@ -284,7 +277,7 @@ make android
@rpath/SwiftGodotRuntime.framework/SwiftGodotRuntime
.
-
- Add GodotIapWrapper as a child node in your scene, then
- use this script:
+ Prefer the post-export fixer when possible; the build phase is only a
+ fallback for projects that cannot run the script after export.
+ With the GodotIapWrapper node from Scene Setup in place,
+ attach this script to confirm the plugin loads and the store connects:
+
+ The typical flow is{' '}
+
+ init_connection
+ {' '}
+ â connect the{' '}
+
+ purchase_updated
+ {' '}
+ and{' '}
+
+ purchase_error
+ {' '}
+ signals â{' '}
+
+ fetch_products
+ {' '}
+ â{' '}
+
+ request_purchase
+ {' '}
+ â{' '}
+
+ finish_transaction
+
+ . See the Purchase Guide for the
+ complete flow.
+
+ The examples below use the iap reference created in Scene
+ Setup (@onready var iap = $GodotIapWrapper).
+
+ GDScript uses snake_case for all function names (
+ init_connection, fetch_products,{' '}
+ request_purchase). Return types use Array{' '}
+ for lists and Variant for platform-specific single
+ results.
+
+ Each call here has a full reference â see the{' '}
+
+ purchase_updated
+ {' '}
+ and{' '}
+
+ purchase_error
+ {' '}
+ signals,{' '}
+
+ init_connection
+
+ , and{' '}
+
+ finish_transaction
+ {' '}
+ for parameters and per-store behavior, and{' '}
+ Error Codes for the error.code{' '}
+ values.
+
finishTransaction{' '}
- after verifying a purchase. On Android, unfinished purchases are
- automatically refunded after 3 days.
- finish_transaction after verifying a
+ purchase. On Android, unfinished purchases are automatically refunded
+ after 3 days.
+
+ Query store metadata with a{' '}
+
+ ProductRequest
+
+ ; results are platform-typed (ProductAndroid on Android,{' '}
+ ProductIOS on iOS):
+
+ See{' '}
+
+ fetch_products
+ {' '}
+ for parameters and per-store behavior.
+
+ Configure both platforms in a single request â the plugin picks the + branch matching the store the game is running on, so one purchase call + works everywhere: +
+ See{' '}
+
+ request_purchase
+ {' '}
+ for parameters and per-store behavior.
+
snake_case for all
- function names (init_connection,{' '}
- fetch_products, request_purchase). Return
- types use Array for lists and Variant for
- platform-specific single results.
-
+ A crash with{' '}
+ Library not loaded: @rpath/GodotIap.framework/GodotIap{' '}
+ means the frameworks were not embedded â see{' '}
+ iOS: Xcode Framework Embedding and run{' '}
+ fix_ios_embed.sh.
+
- kmp-iap provides in-app purchase support for Kotlin
- Multiplatform projects. It supports Android natively and iOS via
- CocoaPods integration.
+ kmp-iap brings OpenIAP-compliant in-app purchases to Kotlin
+ Multiplatform projects. Android talks to Google Play Billing directly;
+ iOS links the OpenIAP StoreKit framework, added with either CocoaPods or
+ Swift Package Manager (see iOS Configuration{' '}
+ below). Requires iOS 15.0+ and the Android minSdk shown in{' '}
+ Android Configuration.
Ensure your shared module has the CocoaPods plugin:
@@ -152,7 +138,6 @@ kotlin { Then runcd iosApp && pod install and always open{' '}
.xcworkspace (not .xcodeproj).
-
@@ -188,9 +165,11 @@ kotlin { + Capability > In-App Purchase
-
- Add to your iosApp/Info.plist:
+ Declaring itms-apps in iosApp/Info.plist is
+ only needed when your own code checks App Store links before opening
+ them â kmp-iap itself does not require it:
+ Results stream through Kotlin Flows: initialize the connection (
+
+ initConnection
+
+ ), attach the purchase and error flows (
+
+ purchaseUpdatedListener
+ {' '}
+ and{' '}
+
+ purchaseErrorListener
+
+ ), then fetch and purchase (
+
+ fetchProducts
+
+ ,{' '}
+
+ requestPurchase
+
+ ,{' '}
+
+ finishTransaction
+
+ ). A successful initConnection() followed by a non-empty{' '}
+ fetchProducts() result is the quickest way to confirm the
+ platform setup above is working. For the full flow, see the{' '}
+ Purchase Guide.
+
Two patterns are supported:
++ Two patterns are supported. The connection, fetch, and purchase APIs + are suspend functions â call them from a coroutine scope: +
+ See{' '}
+
+ initConnection
+ {' '}
+ for parameters and per-store behavior.
+
- KMP IAP uses Kotlin Flow for purchase events:
+ KMP IAP delivers purchase results through hot Kotlin{' '}
+ Flows â despite the names,{' '}
+
+ purchaseUpdatedListener
+ {' '}
+ and{' '}
+
+ purchaseErrorListener
+ {' '}
+ are Flows, not one-shot callbacks. Collect both in a long-lived
+ coroutine scope before requesting a purchase; events are emitted as
+ they occur.
+ For the possible error.code values, see{' '}
+ Error Codes.
+
finishTransaction{' '}
+ finishTransaction
+ {' '}
after verifying a purchase. On Android, unfinished purchases are
automatically refunded after 3 days.
-
+ Fetch products once the connection is up, then request a purchase â
+ the result arrives on purchaseUpdatedListener, not as a
+ return value. See{' '}
+
+ fetchProducts
+ {' '}
+ and{' '}
+
+ requestPurchase
+ {' '}
+ for parameters and per-store behavior.
+
+ Call{' '}
+
+ endConnection()
+ {' '}
+ when the owning screen or scope is disposed â not right after{' '}
+ requestPurchase, or the connection may close before the
+ purchase result arrives.
+
- Package shape: apps reference only{' '}
- OpenIap.Maui. OpenIAP-owned Android and iOS binding
- outputs are flattened into that package. Google Billing, Play
- Services, Gson, AndroidX, and Kotlin Android libraries stay as normal
- NuGet dependencies so your app can deduplicate them with its own
- package graph.
-
- Before you start: create the products in App Store - Connect and Google Play Console first. The product IDs in your MAUI - app must exactly match the store product IDs. + Create the products in App Store Connect and Google Play Console + first. The product IDs in your MAUI app must exactly match the store + product IDs.
Platform setup guides: iOS Setup |{' '} Android Setup
-
- Working from this monorepo before publishing? Use a
- project reference to the main project only. The example app
- re-declares local native references because MSBuild does not
- propagate those transitively through ProjectReference.
- Published NuGet consumers do not need that.
-
+ Your app references a single package, OpenIap.Maui. The
+ OpenIAP-owned iOS and Android bindings are bundled inside it, while
+ shared dependencies (Google Play Billing, Play Services, AndroidX,
+ Kotlin, Gson) remain ordinary NuGet dependencies so NuGet can
+ deduplicate them with the rest of your dependency graph.
+
+ If you are working from this monorepo before publishing, use a project
+ reference to the main project only. The example app re-declares local
+ native references because MSBuild does not propagate those
+ transitively through ProjectReference. Published NuGet
+ consumers do not need that.
+
+ Building the Apple library from source requires Xcode 27; the + published package already embeds a prebuilt XCFramework, so NuGet + consumers do not need Xcode 27 â their normal toolchain is enough. +
+ +net9.0-* TFM to the matching net10.0-* TFM
+ and update the MAUI workload before upgrading the package.
+ + Configure the app project once per platform before calling any store + API. +
+ The typical flow is: initialize the connection with{' '}
+
+ InitConnectionAsync
+
+ , fetch products with{' '}
+
+ FetchProductsAsync
+
+ , register the{' '}
+
+ PurchaseUpdated
+ {' '}
+ and{' '}
+
+ PurchaseError
+ {' '}
+ listeners, request a purchase with{' '}
+
+ RequestPurchaseAsync
+
+ , then finish the verified transaction with{' '}
+
+ FinishTransactionAsync
+
+ . The Purchase Guide covers the
+ complete flow.
+
+ Each call here has a full reference â see{' '}
+
+ InitConnectionAsync
+ {' '}
+ and{' '}
+
+ FetchProductsAsync
+ {' '}
+ for parameters and per-store behavior.
+
Purchase APIs are event-based. Register listeners before calling{' '}
- RequestPurchaseAsync, then finish the transaction only
- after server-side verification succeeds.
+
+ RequestPurchaseAsync
+
+ , then finish the transaction only after server-side verification
+ succeeds. See{' '}
+
+ PurchaseUpdated
+
+ ,{' '}
+
+ PurchaseError
+
+ , and{' '}
+
+ FinishTransactionAsync
+ {' '}
+ for parameters and per-store behavior, and{' '}
+ Error Codes for the error.Code{' '}
+ values.
- Do not skip finishing transactions. On Android, - unfinished purchases are refunded automatically after 3 days. On - iOS, unfinished transactions can be delivered again on the next app - launch. -
-- The MAUI package exposes the same IAPKit helper surface as the - JavaScript SDKs: create a kit client for status, entitlements, and - bind-user calls. These app-facing calls use the publishable key. Store - lifecycle webhooks flow into IAPKit only; there is no outbound webhook - stream in the MAUI package. + IAPKit is OpenIAP's hosted receipt-validation backend (see{' '} + Purchase Verification with IAPKit). + The MAUI package ships the same app-facing helper as the other OpenIAP + SDKs: create a kit client with your publishable key to read purchase + status and entitlements and to bind a purchase to a user. Store + lifecycle events (App Store Server Notifications, Google Play RTDN) + are delivered to IAPKit's backend, not to your app â the client reads + current state through these bounded calls rather than subscribing to a + webhook stream.
Dispose subscriptions and close the store connection when the page or
- service that owns the purchase flow is torn down.
+ service that owns the purchase flow is torn down. See{' '}
+
+ EndConnectionAsync
+ {' '}
+ for its full reference.
+ The example app builds against the in-repo Android library, so rebuild + the OpenIAP Android AARs once before the first run (published-package + consumers skip this step): +
+ Then run the example (its application id is{' '}
+ dev.hyo.martie; uninstalling first clears stale native
+ code):
+
- Upgrading from OpenIap.Maui 1.x? OpenIap.Maui 2.x
- supports .NET 10 only. Retarget every net9.0-* TFM to
- the matching net10.0-* TFM and update the MAUI workload
- before upgrading the package.
-
VS Code launch configurations are available in{' '}
libraries/maui-iap/.vscode/launch.json. The iOS device
@@ -406,6 +478,9 @@ dotnet build -t:Run -f net10.0-maccatalyst`}
#
+
+ Common failures and their causes, roughly in the order you hit them. +
ProductRequest.Skus.
+
+ ProductRequest
+
+ .Skus.
If Google Play shows "This version of the application is not configured for billing through Google Play", the library reached - BillingClient correctly. The app build is not accepted by Play Billing - for that package, signing key, track, tester, or product setup yet. + BillingClient correctly - Play itself rejected the app build. Check + each of the following:
+
Verify the app is running on a signed device build with the matching
bundle identifier and In-App Purchase capability. If you navigate away
- from a purchase screen, call EndConnectionAsync from the
- owning page or lifecycle service so the next screen can initialize a
- fresh store connection.
+ from a purchase screen, call{' '}
+
+ EndConnectionAsync
+ {' '}
+ from the owning page or lifecycle service so the next screen can
+ initialize a fresh store connection.
react-native-iap provides in-app purchase support for React
- Native apps using Nitro Modules. It supports StoreKit 2 on iOS and
- Google Play Billing {GOOGLE_PLAY_BILLING.version}+ on Android by
- default, with optional Horizon and Fire OS Android flavors.
+ Native apps using Nitro Modules, a high-performance native bridging
+ layer for React Native. It supports StoreKit 2 on iOS and Google Play
+ Billing {GOOGLE_PLAY_BILLING.version}+ on Android by default, with
+ optional build flavors for{' '}
+ Horizon OS (Meta Quest) and{' '}
+ Fire OS (Amazon Appstore).
+ react-native-iap v15+ is for bare React Native CLI{' '} + projects only and requires React Native 0.79+ (Nitro + Modules), iOS 15.0+ (StoreKit 2), and{' '} + Android minSdkVersion {ANDROID_SDK.minSdk}+ with{' '} + compileSdkVersion {ANDROID_SDK.compileSdk}+. Expo + support ended in v15.0.0 â use expo-iap{' '} + instead; it provides the same API on Expo Modules. +
+ +react-native-iap v15+
- uses Nitro Modules and requires React Native 0.79+.
- It is designed for bare React Native CLI projects
- only. If you're using Expo, use{' '}
- expo-iap instead.
-
+ react-native-iap requires react-native-nitro-modules as a
+ peer dependency â install both together.
+
react-native-iap v15+ is built on{' '} @@ -108,49 +87,12 @@ npm install react-native-iap`} Native modules are automatically linked during your app's build process
AnyMap.swift using{' '}
- cppPart.pointee.*), pin Swift 5.10 for the{' '}
- NitroModules pod as a temporary workaround:
- react-native-nitro-modules and nitro-codegen{' '}
- to latest, then pod install and do a clean build. If
- issues persist, share a minimal repro (package.json +{' '}
- Podfile) on{' '}
-
- GitHub Issues
-
- .
- + If you hit Swift 6 C++ interop errors in Nitro, see{' '} + Troubleshooting for the Swift 5 + language mode workaround. +
- Bare React Native does not use an Expo config plugin. For Vega OS, - keep Amazon Kepler packages in a Vega-only React Native target and - follow Store Setup for package, manifest, and supported-version - details. + Vega OS is Amazon's newer, non-Android operating system; its apps run + on the Kepler runtime. react-native-iap supports it through a separate + React Native for Vega target â it is not an Android build flavor, and + unlike expo-iap there is no config plugin to enable it. Keep the + Amazon Kepler packages in that Vega-only target so regular iOS and + Android builds are unaffected, and follow{' '} + Amazon Store Setup for package, + manifest, and supported-version details.
@@ -230,6 +172,41 @@ end`} +
+ Under the hood, the typical flow is{' '}
+
+ initConnection
+ {' '}
+ â set up{' '}
+
+ purchaseUpdatedListener
+ {' '}
+ and{' '}
+
+ purchaseErrorListener
+ {' '}
+ â{' '}
+
+ fetchProducts
+ {' '}
+ â{' '}
+
+ requestPurchase
+ {' '}
+ â{' '}
+
+ finishTransaction
+
+ , with{' '}
+
+ endConnection
+ {' '}
+ on teardown. The useIAP hook manages the connection and
+ listener steps for you. See the{' '}
+ Purchase Guide for the complete
+ flow.
+
useIAP methods return{' '}
- Promise<void> and update internal state. Use{' '}
+
+ Each call here has a full reference â see{' '}
+
+ fetchProducts
+
+ ,{' '}
+
+ requestPurchase
+
+ , and{' '}
+
+ finishTransaction
+ {' '}
+ for parameters and per-store behavior, and{' '}
+
+ ErrorCode
+ {' '}
+ for the full error reference.
+
finishTransaction after verifying a purchase.
+ On Android, unfinished purchases are automatically refunded after 3
+ days.
+
+ Most useIAP methods return{' '}
+ Promise<void> and update internal state â use the{' '}
onPurchaseSuccess callback to receive purchase results,
not the return value of requestPurchase.
-
After calling methods, consume state from the hook:
products â Populated after{' '}
+ products (Product[])
+ â Populated after{' '}
fetchProducts()
subscriptions â Populated after fetching with type{' '}
- 'subs'
+ subscriptions (
+ ProductSubscription
+ []) â Populated after fetching with type 'subs'
availablePurchases â Populated after{' '}
+ availablePurchases (
+ Purchase[]) â Populated after{' '}
getAvailablePurchases()
activeSubscriptions â Populated after{' '}
+ activeSubscriptions (
+ ActiveSubscription[])
+ â Populated after{' '}
getActiveSubscriptions()
@@ -392,6 +392,37 @@ purchaseSub.remove();
errorSub.remove();
await endConnection();`}
+
+ Each call here has a full reference â see{' '}
+
+ initConnection
+
+ ,{' '}
+
+ fetchProducts
+
+ ,{' '}
+
+ requestPurchase
+
+ ,{' '}
+
+ finishTransaction
+
+ , and{' '}
+
+ endConnection
+ {' '}
+ for parameters and per-store behavior, and{' '}
+
+ purchaseUpdatedListener
+ {' '}
+ and{' '}
+
+ purchaseErrorListener
+ {' '}
+ for the purchase events.
+
RCT-Folly/folly/Expected.h, add these defines to your{' '}
Podfile post_install block:
-
If you see errors in AnyMap.swift related to{' '}
- cppPart.pointee, see the{' '}
- Nitro Modules section above for the Swift
- 5.10 pin workaround.
+ cppPart.pointee, switch the NitroModules pod
+ to the Swift 5 language mode (SWIFT_VERSION = '5.0') as a
+ temporary workaround:
react-native-nitro-modules{' '}
+ and nitro-codegen to latest, then{' '}
+ pod install and do a clean build. If issues persist,
+ share a minimal repro (package.json +{' '}
+ Podfile) on{' '}
+
+ GitHub Issues
+
+ .
+ next / rc package
- versions while the integration is being finalized.
- next / rc package versions while the
+ integration is being finalized.
+
- Amazon support has two separate targets. Fire OS is an Android Appstore
- build using the amazon Gradle flavor. Vega OS is a Kepler
- runtime target for React Native for Vega and compatible Expo Vega
- builds. Do not use fireOsEnabled as a Vega selector.
+ Amazon distributes apps to two different device families, and OpenIAP
+ treats them as two separate targets. Fire OS is Amazon's Android-based
+ OS (Fire TV, Fire tablets): an Amazon Appstore build is a regular
+ Android build that selects the amazon Gradle flavor. Vega
+ OS is Amazon's newer, non-Android OS: its apps run on the Kepler
+ JavaScript runtime and are built with React Native for Vega (or a
+ compatible Expo Vega build), so Vega is a separate project target rather
+ than an Android flavor.
+ OpenIAP selects each Amazon target through a different mechanism â a + Gradle flavor for Fire OS, a runtime-selected adapter for Vega OS: +
+| Target | +Runtime | +OpenIAP selection | +
|---|---|---|
| Fire OS | +Android / Amazon Appstore SDK | +
+ Android amazon flavor and{' '}
+ openiap-google-amazon.
+ |
+
| Vega OS | +Amazon Kepler JavaScript runtime | +
+ Runtime-selected kepler adapter in{' '}
+ expo-iap or react-native-iap.
+ |
+
Fire OS and Vega OS both validate through Amazon receipts, but their - app metadata is configured in different places. + app metadata is configured in different places. App Tester refers to + Amazon App Tester, Amazon's sideloaded sandbox app for exercising test + purchases on device.
| Target | -Runtime | -OpenIAP selection | -
|---|---|---|
| Fire OS | -Android / Amazon Appstore SDK | -
- Android amazon flavor and{' '}
- openiap-google-amazon.
- |
-
| Vega OS | -Amazon Kepler JavaScript runtime | -
- Runtime-selected kepler adapter in{' '}
- expo-iap or react-native-iap.
- |
-
+ The table below summarizes how each framework selects an Amazon + target; the Fire OS and{' '} + Vega OS sections that follow contain the full + configuration. +
Expo uses android.horizon.appId. Bare React Native
- and Flutter examples below use a Gradle property named{' '}
- horizonAppId and write Android manifest meta-data{' '}
+ reads a Gradle property named horizonAppId; Flutter
+ reads HORIZON_APP_ID from{' '}
+ android/local.properties. Both write Android
+ manifest meta-data{' '}
com.meta.horizon.platform.HORIZON_APP_ID.
|
||||||||||||||||||||||||
| Verification credentials | -Your backend or IAPKit project configuration. | ++ Your backend or IAPKit{' '} + project configuration. + |
Runtime verification payloads use horizon.sku,{' '}
horizon.userId, and{' '}
@@ -81,21 +87,39 @@ function HorizonStoreSetup() {
+ Complete these once per app in Meta Horizon Developer Hub before + configuring any framework: + +
+ Every framework except Godot ships Quest support through the same
+ Android
- Xcode 27 boundary: StoreKit also exposes{' '}
-
+ AppTransaction.all, an async sequence of app-acquisition
- records. OpenIAP 3 does not export that sequence.{' '}
- getAppTransactionIOS returns the current verified app
- transaction, while getAllTransactionsIOS remains in-app
- purchase history; the two histories are not interchangeable.
- AppTransaction.all, an async
+ sequence of app-acquisition records. OpenIAP 3 does not export that
+ sequence. getAppTransactionIOS returns the current
+ verified app transaction, while getAllTransactionsIOS{' '}
+ remains in-app purchase history; the two histories are not
+ interchangeable.
+
-
+
- Availability: billing plan selection requires iOS,
- iPadOS, macOS, tvOS, or visionOS 26.4+ and an app compiled with the
- StoreKit billing-plan APIs available in Xcode 26.5+ / Swift 6.3+. On
- older runtimes, omit billingPlanType and use the store's default billing
+ plan.
+ |
dataAndroid is
- accepted. Custom adapters and fixtures must use the canonical
- field. See{' '}
+ dataAndroid is accepted. Custom adapters and
+ fixtures must use the canonical field. See{' '}
the migration schedule
.
-
- Client-visible data only: Product client payloads
- can be retrieved by apps and must never contain credentials, signing
- keys, or server-authoritative rules. The body is limited to 16 KiB
- measured as UTF-8 bytes. Use an openiap-kit_pk_{' '}
- publishable key in the app. It is extractable and consumes project
- quota, but cannot perform administrative operations. Never embed an{' '}
- openiap-kit_sk_ secret key.
-
openiap-kit_pk_ publishable key in the app. It is
+ extractable and consumes project quota, but cannot perform
+ administrative operations. Never embed an openiap-kit_sk_{' '}
+ secret key.
+ See the{' '} diff --git a/packages/docs/src/pages/docs/types/verify-purchase-with-provider-result.tsx b/packages/docs/src/pages/docs/types/verify-purchase-with-provider-result.tsx index 4dedc0e4b..d799cab89 100644 --- a/packages/docs/src/pages/docs/types/verify-purchase-with-provider-result.tsx +++ b/packages/docs/src/pages/docs/types/verify-purchase-with-provider-result.tsx @@ -1,5 +1,6 @@ import { Link } from 'react-router-dom'; import AnchorLink from '../../../components/AnchorLink'; +import Callout from '../../../components/Callout'; import CodeBlock from '../../../components/CodeBlock'; import LanguageTabs from '../../../components/LanguageTabs'; import SEO from '../../../components/SEO'; @@ -257,15 +258,13 @@ function VerifyPurchaseWithProviderResult() { -
- Public data: Anyone able to call the project's
- client endpoints may receive this payload. Never put secrets,
- credentials, signing keys, or private authorization rules in it.
- Entitlement decisions must continue to use isValid,{' '}
- state, and the store-verified productId.
-
isValid, state, and the store-verified{' '}
+ productId.
+ The field is omitted by default and for invalid receipts, absent or diff --git a/packages/docs/src/pages/docs/updates/announcements.tsx b/packages/docs/src/pages/docs/updates/announcements.tsx index 5aaa7b1f2..b7a1e8e92 100644 --- a/packages/docs/src/pages/docs/updates/announcements.tsx +++ b/packages/docs/src/pages/docs/updates/announcements.tsx @@ -1,5 +1,6 @@ import { useMemo } from 'react'; import { Link } from 'react-router-dom'; +import Callout from '../../../components/Callout'; import SEO from '../../../components/SEO'; import { useScrollToHash, getHashId } from '../../../hooks/useScrollToHash'; import Pagination from '../../../components/Pagination'; @@ -36,14 +37,6 @@ const linkIconStyle = { fontSize: '1.2rem', }; -const calloutStyle = { - marginTop: '1.5rem', - padding: '1rem', - background: 'var(--bg-secondary)', - borderRadius: '0.5rem', - borderLeft: '4px solid var(--primary-color)', -}; - interface Announcement { id: string; date: Date; @@ -159,7 +152,7 @@ function Announcements() { -
+
+
OpenIap.Maui{' '}
- from NuGet or open the{' '}
+ OpenIap.Maui from NuGet or open the{' '}
. See the .NET MAUI setup guide for
full documentation.
- verifyPurchaseWithProvider API with{' '}
+ verifyPurchaseWithProvider API with{' '}
provider: 'iapkit'. See the{' '}
{' '}
for details.
- openiap-google@1.3.0 and later. Check out the{' '}
+ openiap-google@1.3.0 and later. Check out
+ the{' '}
Horizon OS guide
{' '}
for details.
- - Next: We will be publishing quickstart guides and - API references within the Docs â Modules section. -
+- Note: OpenIAP will continue to operate - independently with the same commitment to developer experience and - cross-platform compatibility. Our core libraries remain MIT licensed - and free to use. -
+
Apple / Google â IAPKit
- - IAPKit does not stream webhook events to mobile SDKs. There is no - outbound SSE, WebSocket, push, or long-poll API. Apps should verify - purchases and refresh status or entitlements through the bounded - request/response APIs. If your own backend protects paid resources, - that backend remains responsible for its entitlement decision and any - app push notification. -
-