Today we’re going to learn how to implement in-app purchases using Expo IAP for Android or iOS on React Native.

First we’re going to install the library we need (expo-iap):
npx expo install expo-iap
Now we need to add our code inside app.config.js:
{ "plugins": [ "expo-iap" ] }
Remember to create subscriptions inside Google Play or Apple Store.
Once installed, we will proceed with the code:
import React, { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { View, StyleSheet,Button, ScrollView, Platform, Alert, InteractionManager, FlatList } from "react-native"; import { useAuthContext } from "@/context/AuthContext"; import { useServices } from "@/context/ServicesProvider"; import { AxiosResponse } from "axios"; import { NavigationProp, useNavigation } from "@react-navigation/native"; import { RootParamList } from "@/navigator/AppNavigation"; import Rewarded, { initLoadRewardedAds } from "@/components/widgets/anuncios/Rewarded"; import TextoContenedor from "@/components/elements/TextoContenedor"; import { playCash } from "@/util/SoundManager"; import { vibracionCompra } from "@/util/Vibracion"; import { ProductPurchase, Purchase, requestProducts, useIAP, validateReceipt, getPurchaseHistories } from "expo-iap"; import { ComprasIAP } from "@/objects/ComprasIAP_Obj"; const test = false; const productosSolicitados: string[] = Platform.select({ ios: [ 'producto.1', 'producto.2' ], android: [ 'producto.1', 'producto.2' ], default: [ 'producto.1', 'producto.2' ], }) ?? []; const ComprasIAPScreen: React.FC = ({ }) => { const { t } = useTranslation(); const { userData, creditos, setCreditos } = useAuthContext(); const navigation = useNavigation<NavigationProp<RootParamList>>(); const { comprasQueries, activateLoading, activateLoadingAux } = useServices(); const [monedasPorVideo, setMonedasPorVideo] = useState<number>(0); const [videosRestantes, setVideosRestantes] = useState<number>(0); const [disableVideos, setDisableVideos] = useState<boolean>(false); const [activarRewardedVideo, setActivarRewardedVideo] = useState<boolean>(false); const { connected, products, currentPurchase, currentPurchaseError, requestProducts, requestPurchase, finishTransaction, validateReceipt } = useIAP(); useEffect(() => { if (!connected) return; const clearPendingPurchases = async () => { try { console.log('🧹 Limpiando compras pendientes...'); // Obtener historial de compras según la plataforma const purchaseHistory = await getPurchaseHistories(); if (purchaseHistory && Array.isArray(purchaseHistory)) { console.log(`📋 Encontradas ${purchaseHistory.length} compras en historial`); // Filtrar solo nuestros productos y compras no finalizadas const pendingPurchases = purchaseHistory.filter(purchase => productosSolicitados.includes(purchase.productId) ); console.log(`🔄 Procesando ${pendingPurchases.length} compras pendientes`); // Finalizar cada compra pendiente for (const purchase of pendingPurchases) { try { await finishTransaction({ purchase, isConsumable: true, }); console.log(`Compra finalizada: ${purchase.productId}`); } catch (err) { console.warn(`Error finalizando compra ${purchase.productId}:`, err); } } } else { console.log('No hay historial de compras disponible'); } } catch (err) { console.error('Error limpiando compras pendientes:', err); } }; clearPendingPurchases(); }, [connected]); useEffect(() => { if (!connected) return; const initializeIAP = async () => { try { // Get both products and subscriptions await requestProducts({ skus: productosSolicitados, type: 'inapp' }); //await requestProducts({skus: subscriptionSkus, type: 'subs'}); } catch (error) { console.error('Error initializing IAP:', error); } }; initializeIAP(); }, [connected, requestProducts]); // Handle successful purchases useEffect(() => { if (currentPurchase) { handlePurchaseUpdate(currentPurchase); } }, [currentPurchase]); // Handle purchase errors useEffect(() => { if (currentPurchaseError) { activateLoadingAux.current = false; // Don't show error for user cancellation if (currentPurchaseError.code === 'E_USER_CANCELLED') { return; } Alert.alert( 'Purchase Error', 'Failed to complete purchase. Please try again.', ); console.error('Purchase error:', currentPurchaseError); } }, [currentPurchaseError]); const handlePurchaseUpdate = async (purchase: any) => { try { activateLoadingAux.current = true; console.log('Processing purchase:', purchase); //const transactionId = purchase.id; const transactionReceipt = Platform.OS === 'ios' ? JSON.parse(purchase.transactionReceipt) : JSON.parse(purchase.transactionReceipt); const productId = transactionReceipt.productId; const isValidated = await recordPurchaseInDatabase(purchase, transactionReceipt); if (isValidated) { // Finish the transaction await finishTransaction({ purchase, isConsumable: true, // Set to true for consumable products }); // Update local state (e.g., add bulbs, enable premium features) await updateLocalState(productId); // Show success message showSuccessMessage(productId); } else { Alert.alert( 'Validation Error', 'Purchase could not be validated on server. Please contact support.', ); } } catch (error) { console.error('Error handling purchase:', error); Alert.alert('Error', 'Failed to process purchase.'); } finally { activateLoadingAux.current = false; } }; //Enviar al servidor: const recordPurchaseInDatabase = async (purchase: any, transactionReceipt: any): Promise<boolean> => { const transactionReceiptObj = JSON.parse(purchase.transactionReceipt); let purchaseToken = ""; const productId = transactionReceiptObj.productId; if (Platform.OS === 'ios') { purchaseToken = purchase.purchaseToken; } else { purchaseToken = transactionReceiptObj.purchaseToken; } const packageName = Platform.OS === 'ios' ? "com.your_bundle_id"// obtiene el bundleId : transactionReceipt.packageName; const compra: ComprasIAP = { id_user: userData?.user_id!, purchaseToken: purchaseToken, packageName: packageName, productId: transactionReceipt.productId, transactionId: purchase.transactionId ?? '', purchaseTime: purchase.purchaseTime || Date.now(), platform: Platform.OS, test: test }; try { const response: AxiosResponse<number> = await comprasQueries.comprarCreditosIAP(compra); if (response.data == 1) { console.log("Purchase validated and recorded successfully"); activateLoadingAux.current = false; return true; } else { console.error('Server validation failed:', response.data); return false; } } catch (error) { console.error('Error recording purchase:', error); return false; } }; //Actualizar en local const updateLocalState = async (productId: string) => { // Update your local app state based on the purchase if (productosSolicitados.includes(productId)) { //Obtener el valor de las monedas compradas const monedas = productId.split('.')[1]; console.log(`Adding ${monedas} monedas to user account`); setCreditos(creditos + parseInt(monedas)); } }; const showSuccessMessage = (productId: string) => { InteractionManager.runAfterInteractions(() => { if (productosSolicitados.includes(productId)) { const monedas = productId.split('.')[1]; Alert.alert( 'Thank You!', `${monedas} monedas have been added to your account.`, ); } }); }; // Request purchase for products const solicitarCompraProducto = async (productId: string) => { if (!connected) { Alert.alert( 'Not Connected', 'Store connection unavailable. Please try again later.', ); return; } try { activateLoadingAux.current = true; // Platform-specific purchase request (v2.7.0+) await requestPurchase({ request: { ios: { sku: productId, andDangerouslyFinishTransactionAutomatically: false, }, android: { skus: [productId], }, }, }); } catch (error) { activateLoadingAux.current = false; console.error('Purchase request failed:', error); } }; return ( <View style={styles.contenedor}> {/*Lista de productos*/} <FlatList data={products} renderItem={({ item }) => ( <Button onPress={() => { if (!activateLoadingAux.current) { solicitarCompraProducto(item.id); } }} title="Producto " + item.id + " (" + item.displayPrice?.toString() +")" color="#841584" accessibilityLabel="Learn more about this purple button" /> )} keyExtractor={(item) => item.id} /> </View> ) }; export default ComprasIAPScreen; const styles = StyleSheet.create({ contenedor: { flex: 1, width: '100%', justifyContent: 'center', } });
- Change
const packageName = Platform.OS === 'ios'
- ? "com.your_bundle_id"// obtiene el bundleId
- transactionReceipt.packageName;
And create new object ComprasIAP_Obj:
export interface ComprasIAP_Obj { id_user: number; purchaseToken: string; packageName: string; productId: string; transactionId: string; purchaseTime: number; platform: string; test: boolean; }
You have an example of how to validate a purchase using Python. I recommend validating purchases, although it is not obligatory.
To validate purchases in the back: https://devcodelight.com/validar-compras-en-aplicacion-android-usando-python/
O an example of how to validate a purchase in iOS using PHP: https://devcodelight.com/validar-compra-en-aplicacion-compras-in-app-de-ios-usando-php/
