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

集成Android Billing 4.0.0致React Native IAP失效求助

问题描述

在React Native安卓应用中集成Android Billing 4.0.0时遇到版本冲突问题:

  • 在app/build.gradle中添加implementation 'com.android.billingclient:billing:4.0.0'后,RNIAP组件的const products = await RNIap.getProducts(itemSkus);语句卡住无返回,后续代码无法执行
  • 移除该依赖后,内购功能恢复正常,但Google Play控制台强制要求升级到Billing Library 4或更高版本,导致无法上传应用新版本
  • 已排除模拟器环境问题,线上发布后内购仍无响应;AndroidManifest.xml已添加<uses-permission android:name="com.android.vending.BILLING" />权限,iOS端内购功能完全正常

相关代码

iap.js

import React, { Component } from "react";
import {
    View,
    Text,
    StyleSheet,
    Platform,
    PixelRatio,
} from "react-native";
import {Header,Left,Right,Icon,Content,Grid,Row,Col,Container,H1,Button,Footer} from 'native-base';
import LinearGradient from 'react-native-linear-gradient';

//Redux imports
import { connect } from "react-redux";
import { toggle } from '../../Reducers/toggle';

//In-app Purchase import
import * as RNIap from 'react-native-iap';

const itemSkus = Platform.select({
  ios: [
    'example.for_stackoverflow.ios',
  ],
  android: [
    'example.for_stackoverflow.android',
  ],
});


class Iap extends Component {
  constructor(props) {
    console.log(itemSkus);
    super(props);

    this.state = {
      productList: [],
      receipt: '',
      availableItemsMessage: '',
    };
  }

  state = {
    toggle: this.props.toggle.togglePremium || false,
  };


  handleChange = ( toggle ) => {
    this.setState({ toggle });
  };

  async componentDidMount() {
    try {
      const result = await RNIap.initConnection();
      console.log('result', result);
    } catch (err) {
      console.warn(err.code, err.message);
    }
    this.getItems();
  }

  //after purcahse to to the finish transaction page.
  goToNext = () => {
    this.props.navigation.navigate('Logout', {
      receipt: this.state.receipt,
    });
  }

  getItems = async() => {
    try {
      console.log(itemSkus + ' check itemSkus');
      const products = await RNIap.getProducts(itemSkus);
      //const subscriptions = await RNIap.getSubscriptions(itemSkus);
      // const products = await RNIap.getSubscriptions(itemSkus);
      console.log(products +  'check Products');
      this.setState({ productList: products });
    } catch (err) {
      console.log(err.message + ' err.message');
      console.warn(err.code, err.message);
    }
  }

  buyItem = async(sku) => {
  //console.info('buyItem: ' + sku);
  // const purchase = await RNIap.buyProduct(sku);
  // const products = await RNIap.buySubscription(sku);
  // const purchase = await RNIap.buyProductWithoutFinishTransaction(sku);
  try {
    const purchase: any = await RNIap.buyProduct(sku);
    this.setState({ receipt: purchase.transactionReceipt }, () => this.goToNext());
  } catch (err) {
    //console.warn(err.code, err.message);
    const subscription = RNIap.addAdditionalSuccessPurchaseListenerIOS(async (purchase) => {
      this.setState({ receipt: purchase.transactionReceipt }, () => this.goToNext());
      subscription.remove();
    });
  }
}


  getPurchases = async() => {
  try {
    console.log('get hit, and when??');
    const purchases = await RNIap.getAvailablePurchases();
    let restoredTitles = '';
    let coins = CoinStore.getCount();
    purchases.forEach(purchase => {
      if (purchase.productId == '4dot6OVER01') {
        this.setState({ togglePremium: true });
        restoredTitles += 'Premium Version';
      }
      else if (purchase.productId == 'android.test.purchased') {
        console.log(purchase.productId);
        this.setState({ togglePremium: true });
        restoredTitles += 'Premium Version';
      }
    })
    Alert.alert('Restore Successful', 'You successfully restored the following purchases: ' + restoredTitles);
  } catch(err) {
    //console.warn(err); // standardized err.code and err.message available
    Alert.alert(err.message);
  }
}


    static navigationOptions = {
      drawerIcon : ({tintColor}) => (
        <Icon name="ios-star-outline" style={{fontSize: 24, color: tintColor}} />
      )
    }

    getPrice = (priceString) => {
      const price = JSON.parse(priceString);
      //return (<H1 style={styles.textHeaderNumber}>${price}</H1>)
      return(<Text style={styles.buttonTextBackWhite}>${price}</Text>)
    }

    getTitle = (titleString) => {

      const title = JSON.parse(titleString);
      return(<Row><H1 style={styles.textHeader}>{title}</H1></Row>)
    }

    getDescription = (descString) => {

      const desc = JSON.parse(descString);
      return(<Row><Text style={styles.textDesc}>{desc}</Text></Row>)
    }

    render() {
      const purchase = this.props.toggle.togglePremium;
      const { productList, receipt, availableItemsMessage } = this.state;
      console.log(productList + ' check productList');
      const receipt100 = receipt.substring(0, 100);
        return (

                <Col size={1}>


                      <Row><Text style={styles.textHeader}>Cricket Over Counter Premium</Text></Row>
                      <Row><Text style={styles.textDesc}>Cricket Umpire Over and Ball Counter upgrade to premium to allow all features for the entire innings!</Text></Row>


                  {

                    productList.map((product, i) => {
                      console.log(product + ' check product.');
                      return (
                        <Row key={i} style={{
                          flexDirection: 'column', alignItems: 'center',
                        }}>

                          <Button rounded large warning style={styles.largeButtonGreen}
                              onPress={() => this.buyItem(product.productId)} >
                              {this.getPrice(JSON.stringify(product.price))}
                            </Button>
                        </Row>
                  );
              })
            }


                </Col>
        );
    }
}

const mapStateToProps = state => ({
  toggle: state.toggle,
});

export default connect(mapStateToProps)(Iap);

package.json

{
  "name": "ExampleForStackOverflow",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
    "test": "jest",
    "lint": "eslint ."
  },
  "dependencies": {
    "@invertase/react-native-apple-authentication": "^2.1.2",
    "@react-native-async-storage/async-storage": "^1.13.4",
    "@react-native-firebase/app": "^10.8.0",
    "@react-native-firebase/auth": "^10.8.0",
    "@react-native-firebase/firestore": "^10.8.0",
    "@react-native-google-signin/google-signin": "^7.0.0-alpha.3",
    "@react-native-picker/picker": "^1.16.7",
    "@react-navigation/drawer": "^5.12.3",
    "@react-navigation/native": "^5.9.2",
    "native-base": "^2.15.2",
    "react": "16.13.1",
    "react-compose": "^2.0.0",
    "react-native": "0.63.5",
    "react-native-animatable": "^1.3.3",
    "react-native-apple-authentication": "^2.0.0",
    "react-native-cli": "^2.0.1",
    "react-native-dotenv": "^3.3.1",
    "react-native-draggable-flatlist": "^3.0.7",
    "react-native-elements": "^3.2.0",
    "react-native-gesture-handler": "^1.10.1",
    "react-native-haptic-feedback": "^1.11.0",
    "react-native-iap": "^5.2.14",
    "react-native-keep-awake": "^4.0.0",
    "react-native-linear-gradient": "^2.5.6",
    "react-native-reanimated": "^1.13.4",
    "react-native-restart": "0.0.24",
    "react-native-safe-area-context": "^3.1.9",
    "react-native-safe-area-view": "^1.1.1",
    "react-native-screens": "^2.17.1",
    "react-native-scroll-into-view": "^2.0.2",
    "react-native-splash-screen": "^3.2.0",
    "react-native-swipe-list-view": "^3.2.9",
    "react-native-swipeable-item": "^2.0.2",
    "react-native-switch": "^2.0.0",
    "react-native-vector-icons": "^8.0.0",
    "react-native-webview": "^11.2.3",
    "react-navigation": "^4.4.3",
    "react-navigation-drawer": "^2.6.0",
    "react-redux": "^7.2.2",
    "redux": "^4.0.5",
    "redux-devtools-extension": "^2.13.8",
    "redux-logger": "^3.0.6",
    "redux-persist": "^6.0.0",
    "redux-thunk": "^2.3.0",
    "tcomb-form-native": "^0.6.20"
  },
  "devDependencies": {
    "@babel/core": "^7.8.4",
    "@babel/runtime": "^7.8.4",
    "@react-native-community/eslint-config": "^1.1.0",
    "babel-jest": "^25.1.0",
    "eslint": "^6.5.1",
    "jest": "^25.1.0",
    "metro-react-native-babel-preset": "^0.59.0",
    "react-test-renderer": "16.13.1"
  },
  "jest": {
    "preset": "react-native"
  }
}

解决方案

核心原因

你当前使用的react-native-iap@5.2.14内部依赖的Billing Library版本低于4.0.0,手动添加更高版本的Billing Library会触发版本冲突,导致Native层与JS层通信阻塞,出现调用卡住的问题。

具体修复步骤

  1. 移除手动添加的Billing Library依赖
    从app/build.gradle中删除implementation 'com.android.billingclient:billing:4.0.0'这一行。

  2. 升级react-native-iap到兼容Billing Library 4+的版本
    react-native-iap从6.0.0版本开始正式支持Billing Library 4.x,执行以下命令升级:

    npm install react-native-iap@^6.0.0 --save
    # 或使用yarn
    yarn add react-native-iap@^6.0.0
    
  3. 适配新版本API变更
    6.x版本有部分API调整,需要修改代码适配:

    • initConnection不再返回结果,直接处理Promise状态即可
    • getProducts参数格式保持兼容,但返回的商品对象字段有微调
    • 购买流程的错误处理逻辑需要对应更新

    示例修改componentDidMount:

    async componentDidMount() {
      try {
        await RNIap.initConnection();
        console.log('内购连接初始化成功');
      } catch (err) {
        console.warn(err.code, err.message);
      }
      this.getItems();
    }
    
  4. 验证依赖版本
    执行cd android && ./gradlew app:dependencies查看依赖树,确认com.android.billingclient:billing的版本已自动升级到4.x或更高。

  5. 清理并重建项目

    # 清理安卓构建缓存
    cd android && ./gradlew clean
    # 重新构建应用
    react-native run-android
    

额外注意事项

  • 升级后需在Google Play控制台确认内购商品状态正常,避免因版本变更导致的商品拉取失败
  • 测试时使用搭载最新Google Play服务的真机,模拟器可能存在兼容性问题
  • 若遇到其他API适配问题,可参考react-native-iap版本变更日志调整代码

内容的提问来源于stack exchange,提问作者Andrew Oxley

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 23:17:39