Flutter 应用内购买与付费墙概述

0:02:00

欢迎来到 RevenueCat 的 Flutter SDK Codelab!

目标: 在本 Codelab 中,您将学习如何将 RevenueCat Flutter SDK 集成到新的 Flutter 应用程序中,以获取产品方案并显示预构建的原生付费墙 UI。

您将构建的内容:

一个简单的单屏应用,显示加载指示器,从 RevenueCat 获取您配置的"产品方案",然后显示 PaywallView

前提条件

在开始之前,您必须完成以下设置:

  1. Flutter SDK: 您的 Flutter 开发环境应已准备就绪。
  2. RevenueCat 账户:revenuecat.com 注册的免费账户。
  3. App Store / Play Store 配置:
在 App Store Connect 或 Google Play Console 中创建的应用内产品(订阅或一次性购买)。如果您想了解更多关于配置 Google Play 的信息,请查看 Codelab1:RevenueCat Google Play 集成 在您的 RevenueCat 控制台中将此产品链接到权益产品方案。这是最关键的步骤。如果您没有"当前"产品方案,付费墙将不会显示。
  1. 物理设备或配置好的模拟器: 用于测试应用内购买。

完成本 Codelab 后,您将能够在 Flutter 应用中成功实现应用内购买,并使用 RevenueCat 的 Flutter SDK 显示动态付费墙。

概述

导入 RevenueCat SDK

0:05:00

首先,在实现应用内购买之前,您需要将 RevenueCat SDK 导入到您的现有或新项目中。首先,将以下依赖项添加到您的 pubspec.yaml 文件中:

您可以在 GitHub 上查看最新发布版本

gradle
dependencies:
  purchases_flutter: 9.2.3
  purchases_ui_flutter: 9.2.3

添加这些依赖项后(如下图所示),您应该点击"Pub get"按钮,它将自动下载所需的依赖项。

依赖项

现在,让我们初始化 RevenueCat SDK。我们将在 main.dart 文件中完成此操作。

  1. 打开 lib/main.dart 并用以下代码替换其全部内容。
  2. 将您的 API 密钥粘贴_androidApiKey_iosApiKey 常量中。
dart
// lib/main.dart

import 'package:flutter/material.dart';
import 'package:purchases_flutter/purchases_flutter.dart';
import 'dart:io' show Platform;

// --- 在此处粘贴您的 REVENUECAT 密钥 ---
const String _androidApiKey = "goog_YOUR_KEY_HERE";
const String _iosApiKey = "appl_YOUR_KEY_HERE";

void main() async {
  // 在任何异步插件调用之前确保 Flutter 小部件已初始化
  WidgetsFlutterBinding.ensureInitialized();

  // 在运行应用之前初始化 RevenueCat SDK
  await _initializeRevenueCat();

  runApp(const MyApp());
}

Future<void> _initializeRevenueCat() async {
  // 为开发启用调试日志
  await Purchases.setLogLevel(LogLevel.debug);

  PurchasesConfiguration? configuration;
  if (Platform.isAndroid) {
    configuration = PurchasesConfiguration(_androidApiKey);
  } else if (Platform.isIOS) {
    configuration = PurchasesConfiguration(_iosApiKey);
  } else {
    print("此平台未配置 RevenueCat。");
    return;
  }

  try {
    await Purchases.configure(configuration);
    print("RevenueCat 配置成功!");
  } catch (e) {
    print("配置 RevenueCat 时出错: $e");
  }
}

// ... 后续步骤中的其余代码将放在这里 ...

我们刚刚创建了一个 async main 函数。这允许我们 await _initializeRevenueCat 函数,确保在构建任何 UI 之前 RevenueCat SDK 已配置并准备就绪。这是初始化需要从一开始就可用的插件的正确方式。

太棒了!您现在已经完成了 50% 的实现工作。

验证用户权益

0:03:00

现在让我们继续验证用户权益。

如前所述,权益(Entitlement)代表用户在购买后解锁的访问级别或功能。这对于确定是否显示广告横幅或授予高级访问权限等场景非常有用。

您可以使用以下代码片段轻松检查用户是否拥有活跃的权益:

dart
const ENTITLEMENT_IDENTIFIER = ".."; // 从您的 RevenueCat 控制台获取特定的权益标识符
final customerInfo = await Purchases.getCustomerInfo();
final isEntitled = customerInfo.entitlements.active[ENTITLEMENT_IDENTIFIER]?.isActive;

一旦您检查了用户是否拥有特定权益,就可以根据您应用的商业模式决定如何继续。

例如,如果您的应用是广告支持的,您可能会选择显示或隐藏 AdMob 横幅。或者,您可以选择显示付费墙或购买对话框,让用户解锁高级功能或内容。

以下是如何实现该逻辑的示例:

dart
if (isEntitled == true) {
// 如果用户被授予此权益的访问权限,则不需要显示横幅。
} else {
// 在此处显示横幅 UI 或显示付费墙
..
}

实现应用内购买

0:04:00

现在,让我们实现应用内购买以提供无广告体验。首先,您需要从 RevenueCat 控制台获取相关产品信息。这些产品数据将用于向用户展示购买选项。

您可以通过调用 Purchases.sharedInstance.awaitGetProducts() 来获取可用产品,如以下示例所示:

dart
// 从 RevenueCat 服务器获取产品信息
final List<StoreProduct> products = await Purchases.getProducts(['paywall_tester.subs']);

// 执行应用内购买
final purchaseResult = await Purchases.purchaseStoreProduct(products.first);

如果您提供多种产品变体,例如 paywall_tester.subs:weeklypaywall_tester.subs:monthlypaywall_tester.subs:yearly,您可以使用基础产品标识符 paywall_tester.subs 作为 productIds 字段的值来简化产品获取。这将告诉 RevenueCat 以列表形式获取所有相关产品变体,以便您可以在付费墙 UI 中动态展示它们。

一旦您获取了产品数据,就可以通过调用 Purchases.sharedInstance.awaitPurchase(product) 来启动应用内购买流程。这将自动触发 Google Play 购买对话框,使用户能够在您的应用内完成交易。

就这样,您只用几行代码就集成了一个完整功能的应用内购买流程——无需处理收据、商店 API 或手动购买验证的复杂性。

完整的代码示例如下所示:

dart
import 'package:flutter/services.dart';
import 'package:purchases_flutter/purchases_flutter.dart';

/// 通过产品 ID 获取特定产品并启动购买流程。
///
/// 此函数处理潜在错误,如产品未找到、
/// 用户取消购买或其他商店错误。
Future<void> purchaseProduct() async {
  // 1. 定义要获取的产品标识符。
  const String productId = 'paywall_tester.subs';

  try {
    // 2. 从 RevenueCat 获取 StoreProduct
    print('正在获取产品...');
    final List<StoreProduct> products = await Purchases.getProducts([productId]);

    // 3. 检查产品列表是否不为空
    if (products.isEmpty) {
      print('错误:未找到产品。请检查 ID 和您的 RevenueCat 设置。');
      // 可选:向用户显示错误消息
      return;
    }

    final StoreProduct productToPurchase = products.first;
    print('找到产品: ${productToPurchase.title}。正在启动购买...');

    // 4. 启动购买流程
    final PurchaseResult purchaseResult = await Purchases.purchaseStoreProduct(productToPurchase);

    // 5. 通过验证权益来检查购买是否成功
    // 将 "your_premium_entitlement" 替换为您在 RevenueCat 中的实际权益标识符
    if (purchaseResult.customerInfo.entitlements.all["your_premium_entitlement"]?.isActive ?? false) {
      print('购买成功!用户现在拥有高级访问权限。');
      // 授予高级内容访问权限
    } else {
      print('购买完成,但权益未激活。');
    }
  } on PlatformException catch (e) {
    // 6. 处理潜在错误
    final PurchasesErrorCode error = PurchasesErrorHelper.getErrorCode(e);
    if (error == PurchasesErrorCode.purchaseCancelledError) {
      print('用户取消了购买。');
    } else {
      print('购买失败,错误: ${e.message}');
    }
  } catch (e) {
    print('发生意外错误: $e');
  }
}

实现付费墙

0:07:00

现在,是时候在 Dart 中实现付费墙了。

步骤 1:创建应用外壳

现在,让我们添加基本的 Flutter 应用结构。这包括 MyApp 小部件和一个有状态的 MyHomePage 小部件,最终将承载我们的付费墙。

将以下代码追加到您的 lib/main.dart 文件中:

dart
// lib/main.dart(续)

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'RevenueCat 付费墙演示',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'RevenueCat 付费墙演示'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

// ... State 类将在下一步中构建 ...

步骤 2:获取产品方案并处理状态

这是核心逻辑。在 _MyHomePageState 中,我们将从 RevenueCat 获取产品方案并管理加载和错误状态。

用下面的完整 _MyHomePageState 类替换注释 // ... State 类将在下一步中构建...

dart
// lib/main.dart(续)

class _MyHomePageState extends State<MyHomePage> {
  // 状态变量
  Offering? _offering;
  bool _isLoading = true;
  String? _errorMessage;

  @override
  void initState() {
    super.initState();
    // 页面加载后立即获取产品方案
    _loadOfferings();
  }

  Future<void> _loadOfferings() async {
    // 我们必须确保在调用 setState 之前小部件仍然挂载
    if (!mounted) return;

    setState(() {
      _isLoading = true;
      _errorMessage = null; // 清除之前的错误
    });

    try {
      // 从 RevenueCat 获取当前产品方案
      final offerings = await Purchases.getOfferings();

      // 确保异步调用后小部件仍然挂载
      if (!mounted) return;

      setState(() {
        _offering = offerings.current;
        _isLoading = false;
        if (_offering == null) {
          _errorMessage = "未找到当前产品方案。请检查您的 RevenueCat 控制台。";
        }
      });
    } catch (e) {
      if (!mounted) return;
      setState(() {
        _isLoading = false;
        _errorMessage = "加载产品方案失败: ${e.toString()}";
      });
    }
  }

  // build 方法将在最后一步中完成...
  @override
  Widget build(BuildContext context) {
    // 暂时的占位符
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: const Center(child: Text("即将完成!")),
    );
  }
}
我们刚刚做了什么?
  1. 我们创建了三个状态变量:_isLoading 用于显示加载器,_errorMessage 用于显示错误,_offering 用于保存付费墙的数据。
  2. initState 中,我们调用 _loadOfferings() 立即开始数据获取过程。
  3. _loadOfferings 方法将 Purchases.getOfferings() 调用包装在 try...catch 块中。它更新状态以显示/隐藏加载器,并存储获取的产品方案或错误消息。

步骤 3:显示 PaywallView

最后,让我们更新 build 方法,根据我们的状态变量有条件地显示正确的 UI。当产品方案成功加载后,我们将显示 PaywallView

用以下完整版本替换 _MyHomePageState 中的 build 方法:

dart
// lib/main.dart(在 _MyHomePageState 中)

@override
Widget build(BuildContext context) {
  Widget content;

  // 获取产品方案时显示加载器
  if (_isLoading) {
    content = const Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        CircularProgressIndicator(),
        SizedBox(height: 20),
        Text("正在加载付费墙..."),
      ],
    );
  }
  // 如果出现问题,显示错误消息
  else if (_errorMessage != null) {
    content = Padding(
      padding: const EdgeInsets.all(16.0),
      child: Text(
        _errorMessage!,
        textAlign: TextAlign.center,
        style: const TextStyle(color: Colors.red, fontSize: 16),
      ),
    );
  }
  // 产品方案加载后显示 PaywallView
  else if (_offering != null) {
    content = PaywallView(
      offering: _offering!,
      onDismiss: () => Navigator.pop(context),
      onPurchaseCompleted: (customerInfo) {
        print("购买完成,权益: ${customerInfo.entitlements.active.keys.first}");
        // 您可以在这里关闭或导航离开
      },
      onPurchaseError: (error) {
        print("购买错误: ${error.message}");
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text("购买失败: ${error.message}")),
        );
      },
      onRestoreCompleted: (customerInfo) {
        print("恢复完成。活跃权益: ${customerInfo.entitlements.active.length}");
        if (customerInfo.entitlements.active.isNotEmpty) {
           Navigator.pop(context);
        }
      },
    );
  }
  // 不太可能发生的情况的后备方案
  else {
    content = const Text("没有可用的产品方案。");
  }

  return Scaffold(
    appBar: AppBar(
      backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      title: Text(widget.title),
    ),
    body: Center(
      child: content,
    ),
  );
}

完成!

现在,在设备或模拟器上运行您的应用程序。

sh
flutter run
您应该看到:
  1. 带有加载器的"正在加载付费墙..."消息。
  2. 然后是您完整功能的原生付费墙 UI,如下图所示。
结果

故障排除

如果付费墙未显示,请检查调试控制台并查看这些常见问题: "未找到当前产品方案":这是最常见的错误。这意味着您的 RevenueCat 控制台缺少"当前"产品方案。前往产品方案,选择一个产品方案,并确保它被标记为"当前"。 API 密钥错误:仔细检查您是否复制了正在测试的平台的正确公共 API 密钥。 产品未配置:确保您在商店控制台中的应用内产品已正确设置,并在 RevenueCat 中链接到权益和产品方案。 沙盒/测试用户问题:确保您使用沙盒测试账户登录(iOS)或您的电子邮件已添加为许可证测试人员(Android)。

配置完成!现在,每当用户没有所需权益时,您就可以显示付费墙,使用的正是您在付费墙编辑器中配置的设计。

正如您在 Codelab:RevenueCat Google Play 集成(创建付费墙)中所见,付费墙系统建立在服务端驱动的 UI 之上。这意味着您可以直接从控制台动态更新付费墙的内容和设计,无需推送应用更新或经过审核流程。

总结

在本 Codelab 中,您学习了如何集成 RevenueCat 的 Flutter SDK、实现应用内购买以及在 Flutter 中构建付费墙。现在是时候发布您的应用并赚取更多收入了!

您还可以通过以下资源了解更多关于使用 RevenueCat SDK 的信息:

  • 产品教程:帮助您入门并充分利用 RevenueCat 的视频教程。