You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 11:37:33