# OpenIAP Quick Reference > OpenIAP: Unified in-app purchase specification for iOS & Android > Documentation: https://openiap.dev > Full Reference: https://openiap.dev/llms-full.txt > Generated: 2026-07-16T23:03:55.942Z ## Installation ### React Native / Expo ```bash # expo-iap (Expo projects) npx expo install expo-iap # react-native-iap (React Native CLI) npm install react-native-iap ``` ### Native ```swift // Swift Package Manager .package(url: "https://github.com/hyodotdev/openiap.git", from: "2.4.1") ``` ```kotlin // Gradle implementation("io.github.hyochan.openiap:openiap-google:2.4.1") implementation("io.github.hyochan.openiap:openiap-google-horizon:2.4.1") implementation("io.github.hyochan.openiap:openiap-google-amazon:2.4.1") ``` ```bash # Flutter flutter pub add flutter_inapp_purchase ``` ```gdscript # Godot # Install godot-iap 2.5.1 to addons/godot-iap and enable the plugin ``` ```kotlin // Kotlin Multiplatform implementation("io.github.hyochan:kmp-iap:2.5.1") ``` ```xml ``` Current NuGet package version: 1.3.1 ## Framework Libraries - `expo-iap`: Expo Modules wrapper, same OpenIAP API as React Native. - `react-native-iap`: Nitro Modules wrapper for React Native CLI apps. - `flutter_inapp_purchase`: Dart API with generated OpenIAP types and streams. - `godot-iap`: Godot 4.x plugin with GDScript functions and signals. - `kmp-iap`: Kotlin Multiplatform API with Flow-based purchase events. - `maui-iap`: `OpenIap.Maui` package with `OpenIapClient.Instance`, generated `Types.cs`, IAPKit helpers (`OpenIapClient.KitApi`, `OpenIapClient.ConnectWebhookStream`, `OpenIapClient.ParseWebhookEventData`), flattened OpenIAP-owned iOS xcframework / Android AAR bindings, Google and AndroidX Android dependencies as NuGet package references, and MAUI example flows matching `expo-iap`. ## Core APIs ### Connection ```typescript // Initialize (required before any operation) await initConnection(); // With alternative billing (Android) await initConnection({ alternativeBillingModeAndroid: 'user-choice' }); // Cleanup on unmount await endConnection(); ``` ### Fetch Products ```typescript const products = await fetchProducts({ skus: ['com.app.premium', 'com.app.pro'], type: 'in-app', }); ``` ### Request Purchase ```typescript // IMPORTANT: requestPurchase is event-based, not promise-based // Set up purchaseUpdatedListener before calling await requestPurchase({ request: { apple: { sku: 'com.app.premium' }, google: { skus: ['com.app.premium'] }, }, type: 'in-app', // 'in-app' | 'subs' }); ``` ### Finish Transaction ```typescript // CRITICAL: Must call after verification // Android: purchases auto-refund after 3 days if not acknowledged await finishTransaction({ purchase, isConsumable }); ``` ### Get Available Purchases ```typescript const purchases = await getAvailablePurchases(); // Returns user's current entitlements ``` ### Restore Purchases ```typescript await restorePurchases(); const purchases = await getAvailablePurchases(); ``` ## Events (React Native/Expo) ```typescript import { ErrorCode, purchaseUpdatedListener, purchaseErrorListener, } from 'expo-iap'; // Set up before any purchase request const purchaseUpdateSubscription = purchaseUpdatedListener(async (purchase) => { // 1. Verify purchase on server // 2. Grant entitlement // 3. Finish transaction await finishTransaction({ purchase, isConsumable: false }); }); const purchaseErrorSubscription = purchaseErrorListener((error) => { if (error.code === ErrorCode.UserCancelled) return; // Normal flow console.error('Purchase error:', error.message); }); // Cleanup purchaseUpdateSubscription.remove(); purchaseErrorSubscription.remove(); ``` ## Core Types ### Shared Product Fields ```typescript interface ProductCommon { id: string; // Product identifier (SKU) title: string; // Store title description: string; // Product description displayPrice: string; // Localized price price?: number | null; // Numeric price when available currency: string; // ISO 4217 currency code platform: 'android' | 'ios'; type: 'in-app' | 'subs'; } type Product = ProductAndroid | ProductIOS; type ProductSubscription = ProductSubscriptionAndroid | ProductSubscriptionIOS; ``` ### Purchase ```typescript interface PurchaseCommon { id: string; productId: string; transactionDate: number; purchaseState: PurchaseState; purchaseToken?: string | null; quantity: number; isAutoRenewing: boolean; } type Purchase = PurchaseAndroid | PurchaseIOS; type PurchaseState = 'pending' | 'purchased' | 'unknown'; ``` ### PurchaseError ```typescript interface PurchaseError { code: ErrorCode; // Generated kebab-case error enum message: string; // Human-readable message productId?: string | null; // Related SKU when available debugMessage?: string | null; // Native diagnostic responseCode?: number | null; // Android query response code productIds?: string[] | null; // Android requested product IDs productType?: string | null; // Android product type isEmptyProductList?: boolean | null; // Android query returned no products subResponseCodeAndroid?: SubResponseCodeAndroid | null; // Play purchase-update detail } ``` ## Common Error Codes | Code | Description | Action | |------|-------------|--------| | user-cancelled | User cancelled purchase | No action needed | | duplicate-purchase | A purchase request is already in progress | Wait for the active request instead of starting another | | item-unavailable | Product not in store | Check store config | | already-owned | Already purchased | Restore purchases | | network-error | Network issue | Retry with backoff | | service-error | Store service error | Retry later | | not-prepared | initConnection not called | Call initConnection first | ## API Naming Convention - **Cross-platform**: No suffix (fetchProducts, requestPurchase) - **iOS-only**: `IOS` suffix (syncIOS, getStorefrontIOS) - **Android-only**: `Android` suffix (acknowledgePurchaseAndroid) ## Platform-Specific APIs ### iOS - syncIOS() - Sync with App Store - presentCodeRedemptionSheetIOS() - Show offer code UI - showManageSubscriptionsIOS() - Open subscription management - beginRefundRequestIOS() - Start refund flow ### Android - acknowledgePurchaseAndroid() - Acknowledge purchase - consumePurchaseAndroid() - Consume for re-purchase ## Purchase Flow Summary 1. initConnection() 2. fetchProducts({ skus: [...], type: 'in-app' }) 3. Set up purchaseUpdatedListener 4. requestPurchase({ request: { apple: { sku }, google: { skus: [sku] } }, type: 'in-app' }) 5. In listener: verify -> grant -> finishTransaction() 6. endConnection() on cleanup ## Links - Docs: https://openiap.dev/docs - Types: https://openiap.dev/docs/types - APIs: https://openiap.dev/docs/apis - Errors: https://openiap.dev/docs/errors - GitHub: https://github.com/hyodotdev/openiap