Expo React Native集成react-native-iap遇E_IAP_NOT_AVAILABLE错误
解决Expo React Native中react-native-iap的E_IAP_NOT_AVAILABLE错误
问题背景
在Expo React Native应用中使用react-native-iap时,始终触发E_IAP_NOT_AVAILABLE错误,原因是StoreKit(版本1或2)不可用。已在app.json的plugins中添加react-native-iap,尝试过Expo Go(已知可能不支持仍测试)和TestFlight测试,问题未解决。
依赖包列表
"dependencies": { "@expo/vector-icons": "^14.0.0", "@expo/metro-runtime": "~3.2.1", "@react-native-community/slider": "4.5.2", "@react-native-picker/picker": "2.7.5", "@shopify/react-native-skia": "1.2.3", "expo-asset": "~10.0.8", "expo-constants": "~16.0.2", "expo-image": "~1.12.11", "expo-linear-gradient": "~13.0.2", "expo-linking": "~6.3.1", "expo-router": "~3.5.16", "expo-secure-store": "~13.0.1", "expo-status-bar": "~1.12.1", "react-native-bouncy-checkbox": "^4.0.1", "react-native-dialog": "^9.3.0", "react-native-gesture-handler": "~2.16.1", "react-native-gifted-charts": "^1.4.10", "react-native-iap": "^12.13.2", "react-native-reanimated": "~3.10.1", "react-native-safe-area-context": "4.10.1", "react-native-screens": "3.31.1", "react-native-svg": "15.2.0", "expo-dev-client": "~4.0.16", "react-native-linear-gradient": "*" }
IAP配置代码
import React from "react-native"; import {View} from "react-native"; import { withIAPContext, setup, initConnection, purchaseErrorListener, purchaseUpdatedListener, flushFailedPurchasesCachedAsPendingAndroid, finishTransaction, } from 'react-native-iap'; import {useEffect} from "react"; import {api, getUser} from "../../be"; import * as SecureStore from "expo-secure-store"; function Listener({children}) { // eslint-disable-next-line no-unused-vars let purchaseUpdateSubscription = null; // eslint-disable-next-line no-unused-vars let purchaseErrorSubscription = null; useEffect(() => { void (async () => { console.log('t2') await initConnection().then(() => { // we make sure that "ghost" pending payment are removed // (ghost = failed pending payment that are still marked as pending in Google's native Vending module cache) console.log('t3') flushFailedPurchasesCachedAsPendingAndroid() .catch(() => { console.log('t4') // exception can happen here if: // - there are pending purchases that are still pending (we can't consume a pending purchase) // in any case, you might not want to do anything special with the error }) .then(() => { console.log('t5') purchaseUpdateSubscription = purchaseUpdatedListener( (purchase) => { console.log('purchaseUpdatedListener', purchase); const receipt = purchase.transactionReceipt; if (receipt) { console.log(receipt) api('iap/receipt/', 'POST', purchase.transactionReceipt) .then(async (deliveryResult) => { if (deliveryResult.success === true) { // Tell the store that you have delivered what has been paid for. // Failure to do this will result in the purchase being refunded on Android and // the purchase event will reappear on every relaunch of the app until you succeed // in doing the below. It will also be impossible for the user to purchase consumables // again until you do this. // If consumable (can be purchased again) // await finishTransaction({purchase, isConsumable: true}); // If not consumable await finishTransaction({purchase, isConsumable: false}); // mark subscription as active } else { // Retry / conclude the purchase is fraudulent, etc... } }); } }, ); purchaseErrorSubscription = purchaseErrorListener( (error) => { console.log('t6') console.warn('purchaseErrorListener', error); }, ); }); }).catch((e) => { console.log('t10') console.log(e) // exception can happen here if: // - there are pending purchases that are still pending (we can't consume a pending purchase) // in any case, you might not want to do anything special with the error }); })() }, []) useEffect(() => { return () => { console.log('t7') // Anything in here is fired on component unmount. if (purchaseUpdateSubscription) { purchaseUpdateSubscription.remove(); purchaseUpdateSubscription = null; } if (purchaseErrorSubscription) { purchaseErrorSubscription.remove(); purchaseErrorSubscription = null; } console.log('t7.5') } }, []) return <View>{children}</View> } console.log('t1') setup({storekitMode: 'STOREKIT2_MODE'}) export default withIAPContext(Listener)
可能的遗漏步骤及解决方案
1. 完善Expo插件配置
确保app.json中react-native-iap插件的配置包含StoreKit相关参数,例如:
{ "expo": { "plugins": [ [ "react-native-iap", { "storekitMode": "STOREKIT2_MODE", "bundleIdentifier": "your.bundle.id" } ] ] } }
执行expo prebuild --clean重新生成原生代码后,再构建测试版本。
2. 启用Apple开发者账户相关能力
- 登录Apple Developer Portal,为应用开启In-App Purchase能力。
- 在App Store Connect中创建对应内购项目(即使处于草稿状态,需完成基本信息填写),并关联至应用。
- 添加沙箱测试员:在App Store Connect的「用户和访问」-「沙箱测试员」中创建测试账号,测试时退出设备上的常规Apple ID,使用测试账号登录。
3. StoreKit 2兼容性检查
- 确认Expo SDK 51与react-native-iap v12.x的兼容性,确保原生项目已正确集成StoreKit 2框架。
- 使用真实iOS设备测试,模拟器可能无法完全支持StoreKit 2功能。
4. 代码优化
- 将Android专属的
flushFailedPurchasesCachedAsPendingAndroid()调用用平台判断包裹,避免在iOS上执行无关代码:
import { Platform } from 'react-native'; // ... if (Platform.OS === 'android') { flushFailedPurchasesCachedAsPendingAndroid() // ... } // ...
- 确保
setup()调用在应用启动早期执行,避免因时机问题导致StoreKit未正确初始化。
5. TestFlight测试注意事项
- 提交至TestFlight的版本需正确签名,内购项目状态需为「准备提交」或「已批准」。
- 测试时必须使用沙箱测试账号,不可使用真实Apple ID,否则会触发真实购买流程或导致StoreKit不可用。
内容的提问来源于stack exchange,提问作者Tristan Neate
相关产品推荐
相关产品推荐

