首发于公众号 前端混合开发,欢迎关注。

编者注:本文于2023年9月21日更新,移除了对现已弃用的 expo-in-app-purchases 库的提及。现在建议您使用 react-native-iap 或 react-native-purchases ,我们在本文中都有讨论。

应用内购买(IAP)已经改变了移动应用的盈利方式。它不仅为开发者和企业解锁了新的收入来源,还使开发者能够为用户创造更具吸引力和沉浸感的体验。

有许多基于 Google Play 计费库和苹果的 StoreKit 框架构建的 React Native IAP(应用内购买)包,可以让你轻松地将应用内购买集成到你的应用中。然而,我们将通过一系列步骤来探讨如何将 react-native-iap 集成到你的 React Native 应用中,这涉及构建一个简单的食谱应用程序;在这里找到 GitHub。

概述:应用内购买

应用内购买是用户可以直接在你的应用内购买的额外内容、功能、订阅或服务。换句话说,它是一种应用货币化模型,允许我们免费或以较低成本提供你的应用程序,给予用户无需预先财务承诺就能使用应用的机会,并通过进行应用内购买解锁额外功能或内容的选项。

应用内购买是通过连接到应用商店计费系统的支付网关来实现的。计费和交易过程完全由应用商店管理,使用户能够安全地进行交易。

IAP 提供了极大的灵活性。无论你是在开发电子商务平台、生产力应用还是媒体平台,都可以利用 IAP 来释放你应用的全部货币化潜力。一些常见的应用内购买示例包括:

  • 虚拟产品:如虚拟货币、扩展包、游戏提升等虚拟物品,用于增强用户体验
  • 锁定功能:应用可能有一个免费的基础版本供普通用户使用,以及一个付费的专业版本,该版本提供了只能通过应用内购买解锁的额外功能,以吸引寻求更完整体验的用户
  • 订阅:为了定期获得对独家内容或服务的访问权而收取的周期性费用,即费用是每月的,到了结算周期的末尾,订阅费用会自动收取,以继续获得对产品或服务的访问权。

此外,IAP允许你根据各种条件(如购买历史)为选定的用户群体创建个性化的优惠,以最大化收益,并为你的用户提供更相关的体验。

IAP 的类型

在设置IAP之前,你应该理解有三大类的应用内购买:

  • 消耗品:这种类型的应用内购买是为一次性使用而设计的。因此,用户可以在物品用完后再次购买——例如,虚拟货币、助推器和虚拟礼物。
  • 非消耗品:这些是只需要购买一次并且永久关联到用户账户的物品 - 例如,广告移除,奖励章节和自定义设置
  • 订阅:如前所述,这种应用内购买方式允许用户定期支付重复费用,以获取高级功能或独家内容的访问权限

关于我们的食谱应用程序

要顺利跟随本文进行,你应该具备以下条件:

  • React Native的知识
  • 一个谷歌开发者账户
  • 在你的开发机器上设置React Native环境
  • 一个安卓设备

为了演示如何将应用内购买集成到React Native应用中,我们将开发一个食谱应用,该应用为免费用户显示有限的食谱列表,并提供应用内购买选项,通过高级订阅解锁所有食谱。

该应用程序由三个屏幕组成:

  • 首页:显示食谱列表,并包含一个按钮,该按钮会引导用户到付费墙屏幕,在那里用户可以购买高级订阅
  • 菜谱详情:这个界面展示了菜谱的详细概览,包括所需的食材和制作步骤
  • 付费墙:这显示了用户可以购买的可用内购产品

应用程序的初始项目在GitHub上可用。我们将在其基础上开发应用程序内购买部分,这将贯穿文章的其余部分。

运行以下命令来克隆启动项目:

git clone -b start https://github.com/emmanuelhashy/RecipeApp.git --single-branch

创建我们的IAP产品

要将IAP集成到你的应用程序中,你首先需要在各自的商店中设置产品。这些商店作为一个中心,用于配置和管理在你的应用程序中向用户展示的产品。

在这个例子中,我们将介绍如何在Google Play控制台为Android设备设置产品的步骤。为了在开发过程中无缝测试应用内购买,你需要将你的应用发布到内部测试轨道

在Google Play控制台中,设置一个商户账户以在您的应用中接受付款。之后,在“All apps”页面的已发布应用列表中选择您的应用。

image.png

在应用程序仪表板中,滚动到侧边栏的 创收 部分并选择 商品 下拉按钮。可以根据您的IAP产品类型选择 应用内产品订阅 。在这个例子中,我们将选择“应用内产品”。

image.png

现在,在应用内产品页面上,点击创建产品按钮以创建新产品:

image.png

提供产品所需的属性,包括产品 ID、名称、描述和价格。之后,点击“保存”,然后点击“激活”以使产品可供购买。你会想复制产品的 ID,因为我们稍后会在应用中使用它:

image.png

你现在应该能在产品概览页面看到你的产品。你可以在此页面创建尽可能多的产品。

添加许可证测试员

许可证测试员是一个账户,允许你在受控环境中与应用内购买进行交互。在开发过程中,你可以使用许可证测试员来测试和验证你的应用内购买是否正常工作,而无需进行实际支付。

在你的 Google Play 控制台中,导航至许可证测试页面,可以通过在搜索栏中搜索,或者从主控制台的仪表板侧边栏选择许可证测试:

image.png

然后,点击“创建电子邮件列表”按钮,并为列表提供一个名称和测试账户的电子邮件地址。点击“保存更改”。

image.png

接下来,在应用程序仪表板的内部测试部分,点击测试人员选项卡,并添加您的测试人员列表:

image.png

选择你之前创建的列表,然后点击保存:

image.png

接下来,点击页面底部的“复制链接”按钮,以复制给你的测试人员的邀请URL:

image.png

最后,在带有测试账户的设备上打开URL以接受邀请。

安装 react-native-iap

react-native-iap 库允许你在你的 React Native 应用中无缝实现应用内购买。它是围绕 Google Play 计费库和苹果 StoreKit 框架的封装。此外,通过这个库,你还可以集成来自亚马逊应用商店的 IAP 项目。

首先,在终端中运行以下命令来使用npm安装包:

npm install react-native-iap

安装后,需要进行一些额外的配置以完成包的设置。请前往 android/build.gradle ,并在 buildscript.ext 块中添加以下属性:

androidXAnnotation = "1.1.0"
androidXBrowser = "1.0.0"
minSdkVersion = 24
kotlinVersion = "1.6.0"

然后,在依赖项块下面添加以下内容:

classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"

react-native-iap 允许你整合来自Google Play商店和Amazon Appstore的IAP产品。要启用Play商店的支付功能,请导航至 android/app/build.gradle ,然后在 defaultConfig 块下添加以下内容:

missingDimensionStrategy "store", "play"

或者,你可以通过在Android块中添加以下属性来启用两个商店的支付功能:

 flavorDimensions "appstore"
 productFlavors {
   googlePlay {
     dimension "appstore"
     missingDimensionStrategy "store", "play"
   }
   amazon {
     dimension "appstore"
     missingDimensionStrategy "store", "amazon"
   }
 }

初始化连接

react-native-iap 库的原生模块需要在应用程序生命周期的早期进行初始化,即在进行任何与IAP相关的函数调用之前。这可以在应用程序的根组件中完成。

为了实现这个,将以下代码添加到你的 App 组件中:

useEffect(() => {
  const init = async () => {
    try {
      await initConnection();
      if (Platform.OS === 'android') {
        flushFailedPurchasesCachedAsPendingAndroid();
      }
    }
    catch (error) {
      console.error('Error occurred during initilization', error.message);
    }
  }
  init();
  return () => {
    endConnection();
  }
}, [])

在上述代码中,调用了 initConnection 函数来初始化包的原生模块。

另外,在文件中包含以下导入:

import React, { useEffect } from 'react';
import { Platform } from 'react-native';
import {
  initConnection,
  endConnection,
  flushFailedPurchasesCachedAsPendingAndroid,
} from 'react-native-iap';

定义产品SKU

产品ID是集成应用内购买的关键组成部分。每一个都能唯一地标识一个可购买的产品。由于我们的应用内购买产品的ID将在应用的不同地方使用,我们需要在一个中心位置定义它。

src/utils 下创建一个名为 constants.js 的新文件,并添加以下代码:

import { Platform } from "react-native"
const productSkus = Platform.select({
    android: [
        'recipe_app_premium'
    ]
})
export const constants = {
    productSkus
};

在这里,我们定义了一个常量 productSkus ,借助 Platform.select 函数,正确地识别出与设备操作系统对应的产品ID。我们还提供了之前在 Google Play 控制台中创建的产品的产品ID。

获取可用购买项

react-native-iap 提供了 getAvailablePurchases 功能,用于检查用户当前的购买情况。我们可以使用这种方法来验证用户是否拥有高级订阅,以解锁所有食谱的访问权限。

打开 src/screens/home.jsx 并在 Home 组件中添加以下代码:

useFocusEffect(
    useCallback(() => {
        setLoading(true);
        const getPurchase = async () => {
            try {
                const result = await getAvailablePurchases();
                const hasPurchased = result.find((product) => product.productId === constants.productSkus[0]);
                setLoading(false);
                setPremiumUser(hasPurchased);
            }
            catch (error) {
                console.error('Error occurred while fetching purchases', error);
            }
        }

        getPurchase();

    }, [])
)

useFocusEffect 钩子内部,我们定义了一个 getPurchase 函数,该函数获取用户的可用购买并设置 isPremiumUser 状态为真,如果用户购买的产品的ID与我们的高级产品的ID匹配。

另外,不要忘记包含以下的导入:

import { getAvailablePurchases } from "react-native-iap";
import { constants } from "../utils/constants";

添加内购产品

目前,付费墙屏幕并未显示其后面的可用IAP产品。为了在这个屏幕上展示我们的IAP产品,让我们开始创建一个 ProductItem 组件来代表每一个IAP项目。

src/components 中创建一个名为 productItem.js 的文件。然后,添加以下代码:

import React from "react";
import { View, StyleSheet, Text, Button } from "react-native";

const ProductItem = ({ title, onPress }) => {
    return (
        <View style={styles.container}>
            <Text style={styles.title}>{title}</Text>
            <View style={styles.button}><Button title='Buy' color='coral' onPress={onPress}/></View>
        </View>
    )
}
export default ProductItem;

该组件有两个属性 —— title ,是产品的名称,和 onPress ,一个启动购买的回调函数。

接下来,添加组件样式:

const styles = StyleSheet.create({
    container: {
        flexDirection: 'row',
        backgroundColor: '#fff',
        height: 100,
        borderRadius: 10,
        elevation: 6,
        justifyContent: 'space-between',
        alignItems: 'center',
        padding: 10,
        marginTop: 30,
        marginHorizontal: 10
    },
    title: {
        color: '#000',
        fontSize: 16,
        flex: 2.5,
        marginRight: 10
    },
    button: {
        flex: 1
    }
});

然后,打开 src/screens/paywall.jsx 并在 Paywall 组件中添加以下内容:

const [products, setProducts] = useState([]);
const [isLoading, setLoading] = useState(true);
useEffect(() => {
      const  purchaseUpdateSubscription = purchaseUpdatedListener(
          async (purchase) => {
              const receipt = purchase.transactionReceipt;
              if (receipt) {
                  try {
                      await finishTransaction({ purchase, isConsumable: false });
                  } catch (error) {
                      console.error("An error occurred while completing transaction", error);
                  }
                  notifySuccessfulPurchase();
              }
          });
      const purchaseErrorSubscription = purchaseErrorListener((error) =>
          console.error('Purchase error', error.message));
      const fetchProducts = async () => {
          try {
              const result = await getProducts({ skus: constants.productSkus });
              setProducts(result);
              setLoading(false);
          }
          catch (error) {
              Alert.alert('Error fetching products')
          }
      }
      fetchProducts();
      return () => {
          purchaseUpdateSubscription.remove();
          purchaseErrorSubscription.remove();
      }

  }, [])

我们分解上面的代码块是做什么的:

  • 首先,我们定义两个状态 — productsisLoading — 用于存储IAP项目和正在获取的IAP项目的加载状态
  • useEffect 钩子中,我们定义了两个监听器 — purchaseUpdatedListenerpurchaseError — 来监听成功的购买和在过程中可能发生的任何错误。通常,你应该在启动任何购买事件之前注册这些监听器。
  • purchaseUpdatedListener 中,我们定义了一些自定义逻辑来处理与我们后端的收据验证

在钩子中,我们使用 fetchProduct 函数获取可用的产品,该函数围绕 react-native-iap 中的 getProducts 函数。然后,产品的状态会根据返回结果进行更新。

最后,我们确保在组件卸载时移除监听器。

接下来,添加以下函数,该函数在购买完成时执行:

const notifySuccessfulPurchase = () => {
    Alert.alert("Success", "Purchase successful", [
        {
            text: 'Home',
            onPress: () => navigation.navigate('Home')
        }
    ])
}

然后,添加下面的代码来处理购买:

const handlePurchase = async (productId) => {
    setPurchaseLoading(true)
    try {
        await requestPurchase({ skus: [productId] });
    } catch (error) {
        Alert.alert('Error occurred while making purchase')
    }
    finally {
        setLoading(false);
    }
}

上述函数通过提供正在购买的商品的产品ID来调用 requestPurchase 函数,以启动一个IAP。

更新付费墙

最后,将付费墙的JSX更新为以下内容:

<View style={styles.container}>
    {
        !isLoading ?
            <>
                <View style={styles.header}>
                    <Image source={backgroundImage} style={styles.image} />
                    <View style={styles.heading}>
                        <Text style={styles.text}>Unlock all Recipes</Text>
                        <Text style={styles.subText}>Get unlimited access to 1000+ recipes</Text>
                    </View>
                </View>
                {products.map((product, index) => (
                    <ProductItem
                        key={index}
                        title={product.title}
                        onPress={() => handlePurchase(product.productId)} />
                ))}
            </> :
            <View style={styles.indicator}>
                <ActivityIndicator size='large' />
            </View>
    }
</View>

在文件中包含以下导入:

import React, { useEffect, useState } from "react";
import { View, StyleSheet, Text, Image, Alert, ActivityIndicator } from "react-native";
import { constants } from "../utils/constants";
import {
    getProducts, //For fetching available products
    requestPurchase, //For initiating in-app purchases
    purchaseUpdatedListener, //For listening to purchase events
    purchaseErrorListener, //For listening to purchase errors
    finishTransaction  //For acknowledging a purchase
} from "react-native-iap";
import ProductItem from "../components/productItem";

运行 React Native 应用

现在你已经完成了应用内购买的设置,是时候运行应用程序了。为了做到这一点,确保你的安卓设备已连接到你的开发机器。虽然你可以使用模拟器来实现这一点,但我们建议你在真实设备上测试应用内购买。

现在,在你的终端运行以下命令:

npx run react-native run-android

你应该能看到与下面类似的输出,并能够发起应用内购买:

image.png

总结

在你的React Native应用程序中实现应用内购买可以解锁额外的收入来源,而且,如果实现得当,还可以提升你的用户体验。

我们已经通过逐步指导,介绍了如何使用 react-native-iap 库轻松地将应用内购买集成到您的React Native应用中。如果您想考虑另一种方法,或者想在iOS应用中实现IAP,请查看我们关于使用expo-in-app-purchases实现IAP的帖子。

交流

首发于公众号 大迁世界,欢迎关注。📝 每周一篇实用的前端文章 🛠️ 分享值得关注的开发工具 ❓ 有疑问?我来回答

本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试完整考点、资料以及我的系列文章。


王大冶
68.1k 声望105k 粉丝