import {NativeModules, Platform} from 'react-native'; import * as IapAmazon from './modules/amazon'; import * as IapAndroid from './modules/android'; import * as IapIos from './modules/ios'; import * as IapIosSk2 from './modules/iosSk2'; import {singleProductAndroidMap} from './types/android'; import {offerToRecord} from './types/apple'; import { offerSk2Map, ProductSk2, productSk2Map, subscriptionSk2Map, transactionSk2ToPurchaseMap, } from './types/appleSk2'; import { fillProductsWithAdditionalData, getAndroidModule, getAndroidModuleType, getIosModule, getNativeModule, isAmazon, isIosStorekit2, storekit1Mode, storekit2Mode, storekitHybridMode, } from './internal'; import { Product, ProductPurchase, ProductType, ProrationModesAmazon, Purchase, PurchaseResult, PurchaseStateAndroid, RequestPurchase, RequestSubscription, Subscription, SubscriptionAmazon, SubscriptionAndroid, SubscriptionIOS, SubscriptionPlatform, SubscriptionPurchase, } from './types'; export {IapAndroid, IapAmazon, IapIos, IapIosSk2, isIosStorekit2}; const {RNIapIos, RNIapIosSk2, RNIapModule, RNIapAmazonModule} = NativeModules; const ANDROID_ITEM_TYPE_SUBSCRIPTION = ProductType.subs; const ANDROID_ITEM_TYPE_IAP = ProductType.inapp; /** * STOREKIT1_MODE: Will not enable Storekit 2 even if the device supports it. Thigs will work as before, * minimum changes required in the migration guide (default) * HYBRID_MODE: Will enable Storekit 2 for iOS devices > 15.0 but will fallback to Sk1 on older devices * There are some edge cases that you need to handle in this case (described in migration guide). This mode * is for developers that are migrating to Storekit 2 but want to keep supporting older versions. * STOREKIT2_MODE: Will *only* enable Storekit 2. This disables Storekit 1. This is for apps that * have already targeted a min version of 15 for their app. */ export type STOREKIT_OPTIONS = | 'STOREKIT1_MODE' | 'STOREKIT_HYBRID_MODE' | 'STOREKIT2_MODE'; export const setup = ({ storekitMode = 'STOREKIT1_MODE', }: { storekitMode?: STOREKIT_OPTIONS; } = {}) => { switch (storekitMode) { case 'STOREKIT1_MODE': storekit1Mode(); break; case 'STOREKIT2_MODE': storekit2Mode(); break; case 'STOREKIT_HYBRID_MODE': storekitHybridMode(); break; default: break; } }; /** * Init module for purchase flow. Required on Android. In ios it will check whether user canMakePayment. * ## Usage ```tsx import React, {useEffect} from 'react'; import {View} from 'react-native'; import {initConnection} from 'react-native-iap'; const App = () => { useEffect(() => { void initConnection(); }, []); return ; }; ``` */ export const initConnection = (): Promise => getNativeModule().initConnection(); /** * Disconnects from native SDK * Usage * ```tsx import React, {useEffect} from 'react'; import {View} from 'react-native'; import {endConnection} from 'react-native-iap'; const App = () => { useEffect(() => { return () => { void endConnection(); }; }, []); return ; }; ``` * @returns {Promise} */ export const endConnection = (): Promise => getNativeModule().endConnection(); /** * Consume all 'ghost' purchases (that is, pending payment that already failed but is still marked as pending in Play Store cache). Android only. * @returns {Promise} */ export const flushFailedPurchasesCachedAsPendingAndroid = (): Promise => getAndroidModule().flushFailedPurchasesCachedAsPending(); /** * Get a list of products (consumable and non-consumable items, but not subscriptions) ## Usage ```ts import React, {useState} from 'react'; import {Platform} from 'react-native'; import {getProducts, Product} from 'react-native-iap'; const skus = Platform.select({ ios: ['com.example.consumableIos'], android: ['com.example.consumableAndroid'], }); const App = () => { const [products, setProducts] = useState([]); const handleProducts = async () => { const items = await getProducts({skus}); setProducts(items); }; useEffect(() => { void handleProducts(); }, []); return ( <> {products.map((product) => ( {product.productId} ))} ); }; ``` Just a few things to keep in mind: - You can get your products in `componentDidMount`, `useEffect` or another appropriate area of your app. - Since a user may start your app with a bad or no internet connection, preparing/getting the items more than once may be a good idea. - If the user has no IAPs available when the app starts first, you may want to check again when the user enters your IAP store. */ export const getProducts = ({ skus, }: { skus: string[]; }): Promise> => { if (!skus?.length) { return Promise.reject('"skus" is required'); } return ( Platform.select({ ios: async () => { let items: Product[]; if (isIosStorekit2()) { items = ((await RNIapIosSk2.getItems(skus)) as ProductSk2[]).map( productSk2Map, ); } else { items = (await RNIapIos.getItems(skus)) as Product[]; } return items.filter((item: Product) => skus.includes(item.productId)); }, android: async () => { const products = ( await getAndroidModule().getItemsByType(ANDROID_ITEM_TYPE_IAP, skus) ).map(singleProductAndroidMap); return fillProductsWithAdditionalData(products); }, }) || (() => Promise.reject(new Error('Unsupported Platform'))) )(); }; /** * Get a list of subscriptions * ## Usage ```tsx import React, {useCallback} from 'react'; import {View} from 'react-native'; import {getSubscriptions} from 'react-native-iap'; const App = () => { const subscriptions = useCallback( async () => await getSubscriptions({skus:['com.example.product1', 'com.example.product2']}), [], ); return ; }; ``` */ export const getSubscriptions = ({ skus, }: { skus: string[]; }): Promise => { if (!skus?.length) { return Promise.reject('"skus" is required'); } return ( Platform.select({ ios: async (): Promise => { let items: SubscriptionIOS[]; if (isIosStorekit2()) { items = ((await RNIapIosSk2.getItems(skus)) as ProductSk2[]).map( subscriptionSk2Map, ); } else { items = (await RNIapIos.getItems(skus)) as SubscriptionIOS[]; } items = items.filter((item: SubscriptionIOS) => skus.includes(item.productId), ); return addSubscriptionPlatform(items, SubscriptionPlatform.ios); }, android: async (): Promise => { const androidPlatform = getAndroidModuleType(); let subscriptions = (await getAndroidModule().getItemsByType( ANDROID_ITEM_TYPE_SUBSCRIPTION, skus, )) as SubscriptionAndroid[] | SubscriptionAmazon[]; switch (androidPlatform) { case 'android': { const castSubscriptions = subscriptions as SubscriptionAndroid[]; return addSubscriptionPlatform( castSubscriptions, SubscriptionPlatform.android, ); } case 'amazon': let castSubscriptions = subscriptions as SubscriptionAmazon[]; castSubscriptions = await fillProductsWithAdditionalData( castSubscriptions, ); return addSubscriptionPlatform( castSubscriptions, SubscriptionPlatform.amazon, ); case null: default: throw new Error( `getSubscriptions received unknown platform ${androidPlatform}. Verify the logic in getAndroidModuleType`, ); } }, }) || (() => Promise.reject(new Error('Unsupported Platform'))) )(); }; /** * Adds an extra property to subscriptions so we can distinguish the platform * we retrieved them on. */ const addSubscriptionPlatform = ( subscriptions: T[], platform: SubscriptionPlatform, ): T[] => { return subscriptions.map((subscription) => ({...subscription, platform})); }; /** * Get all purchases made by the user (either non-consumable, or haven't been consumed yet) * ## Usage ```tsx import React, {useCallback} from 'react'; import {View} from 'react-native'; import {getAvailablePurchases} from 'react-native-iap'; const App = () => { const availablePurchases = useCallback( async () => await getAvailablePurchases(), [], ); return ; }; ``` ## Restoring purchases You can use `getAvailablePurchases()` to do what's commonly understood as "restoring" purchases. :::note For debugging you may want to consume all items, you have then to iterate over the purchases returned by `getAvailablePurchases()`. ::: :::warning Beware that if you consume an item without having recorded the purchase in your database the user may have paid for something without getting it delivered and you will have no way to recover the receipt to validate and restore their purchase. ::: ```tsx import React from 'react'; import {Button} from 'react-native'; import {getAvailablePurchases,finishTransaction} from 'react-native-iap'; const App = () => { handleRestore = async () => { try { const purchases = await getAvailablePurchases(); const newState = {premium: false, ads: true}; let titles = []; await Promise.all(purchases.map(async purchase => { switch (purchase.productId) { case 'com.example.premium': newState.premium = true; titles.push('Premium Version'); break; case 'com.example.no_ads': newState.ads = false; titles.push('No Ads'); break; case 'com.example.coins100': await finishTransaction({purchase}); CoinStore.addCoins(100); } })); Alert.alert( 'Restore Successful', `You successfully restored the following purchases: ${titles.join(', ')}`, ); } catch (error) { console.warn(error); Alert.alert(error.message); } }; return (