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

Flutter中使用in_app_purchase 3.1.7检查用户有效订阅

解决in_app_purchase 3.1.7版本检查有效订阅的问题

你的现有代码存在几个核心问题:

  • 仅监听实时购买流,但App启动时不会自动返回历史订阅记录,必须主动触发恢复购买操作才能获取过往订阅信息
  • 用pendingCompletePurchase判断订阅有效性完全错误,这个字段只是标记是否需要调用completePurchase()完成交易,和订阅是否有效无关
  • checkSubscriptionStatus里的setState写法无效,没有实际更新状态

正确实现步骤

1. 主动恢复历史购买记录

App启动时必须调用恢复购买接口,才能拉取用户之前的订阅记录。恢复操作的结果会通过purchaseStream返回,所以要确保提前监听了该流。

2. 正确判断订阅有效性

针对订阅类型的PurchaseDetails,先通过productID确认是目标订阅,再通过解析收据获取订阅的过期时间,对比当前时间判断是否有效。注意iOS和Android的收据格式不同,建议后端验证避免客户端篡改风险。

3. 完整代码示例

import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:flutter/material.dart';
import 'dart:convert';

class SubscriptionStatusPage extends StatefulWidget {
  const SubscriptionStatusPage({Key? key}) : super(key: key);

  @override
  State<SubscriptionStatusPage> createState() => _SubscriptionStatusPageState();
}

class _SubscriptionStatusPageState extends State<SubscriptionStatusPage> {
  final InAppPurchase _inAppPurchase = InAppPurchase.instance;
  bool _isPremiumSubscribed = false;
  late StreamSubscription<List<PurchaseDetails>> _purchaseStreamSubscription;

  @override
  void initState() {
    super.initState();
    // 监听所有购买/恢复事件
    _purchaseStreamSubscription = _inAppPurchase.purchaseStream.listen(
      _handlePurchaseUpdates,
      onError: (err) => print('购买流异常: $err'),
    );
    // App启动时自动恢复历史购买
    _restoreExistingPurchases();
  }

  @override
  void dispose() {
    _purchaseStreamSubscription.cancel();
    super.dispose();
  }

  // 恢复历史购买记录
  Future<void> _restoreExistingPurchases() async {
    if (await _inAppPurchase.isAvailable()) {
      await _inAppPurchase.restorePurchases();
    }
  }

  // 处理购买/恢复的状态更新
  void _handlePurchaseUpdates(List<PurchaseDetails> purchaseDetailsList) {
    for (final purchase in purchaseDetailsList) {
      switch (purchase.status) {
        case PurchaseStatus.purchased:
        case PurchaseStatus.restored:
          // 必须完成交易,否则iOS会重复触发该事件
          if (purchase.pendingCompletePurchase) {
            _inAppPurchase.completePurchase(purchase);
          }
          // 验证目标订阅是否有效
          _checkSubscriptionValidity(purchase);
          break;
        case PurchaseStatus.error:
          print('购买失败: ${purchase.error?.message}');
          break;
        case PurchaseStatus.canceled:
          // 用户取消购买,更新订阅状态
          if (purchase.productID == 'premium') {
            setState(() => _isPremiumSubscribed = false);
          }
          break;
        default:
          break;
      }
    }
  }

  // 验证订阅有效性
  void _checkSubscriptionValidity(PurchaseDetails purchase) {
    if (purchase.productID != 'premium') return;

    final verificationData = purchase.verificationData;
    if (verificationData.source == VerificationSource.googlePlay) {
      // Android端解析Google Play收据
      try {
        final receiptJson = jsonDecode(verificationData.localVerificationData);
        final expiryTimeMillis = int.parse(
          receiptJson['purchaseDetails']['expiryTimeMillis'].toString()
        );
        final now = DateTime.now().millisecondsSinceEpoch;
        setState(() {
          _isPremiumSubscribed = expiryTimeMillis > now;
        });
      } catch (e) {
        print('Android收据解析失败: $e');
        setState(() => _isPremiumSubscribed = false);
      }
    } else if (verificationData.source == VerificationSource.appStore) {
      // iOS端建议将Base64收据发送到后端,由后端调用App Store API验证
      // 此处为示例逻辑,实际需替换为后端验证结果
      setState(() => _isPremiumSubscribed = true);
    }
  }

  // 查询订阅商品信息(保留你的原有逻辑)
  Future<void> loadSubscriptionProducts() async {
    final productIds = {'premium'};
    final response = await _inAppPurchase.queryProductDetails(productIds);
    print('订阅商品列表: ${response.productDetails}');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text(
          _isPremiumSubscribed ? '已开通Premium订阅' : '未开通Premium订阅',
          style: const TextStyle(fontSize: 18),
        ),
      ),
    );
  }
}

关键注意事项

  • 必须调用completePurchase():所有pendingCompletePurchase为true的交易都要调用该方法完成,否则iOS会在App重启后重复触发该购买事件。
  • 优先后端验证收据:客户端解析收据存在被篡改的风险,建议将verificationData发送到你的后端,由后端调用官方API验证订阅状态。
  • 定期重新验证:订阅过期后平台不会主动通知客户端,建议在App启动、订阅到期前1天等节点重新验证订阅状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 00:11:07