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

Flutter Firebase订阅内购自动续费监听与过期日期更新方案

处理Flutter订阅内购自动续费的方案

你当前的代码仅能监听首次购买或APP前台时的购买事件,但自动续费通常在后台完成,APP未启动或在后台时无法通过purchaseStream收到事件。以下是可靠的解决方案:


一、客户端主动校验订阅状态(基础保障)

每次APP启动或回到前台时,主动拉取用户历史购买记录,解析订阅的真实过期日期并更新Firebase。

修改后的客户端代码

@override
void initState() {
  super.initState();
  loadPlans();
  _initSubscriptionListener();
  // 启动时立即校验订阅状态
  _checkSubscriptionStatus();
  // 监听APP生命周期,回到前台时再次校验
  WidgetsBinding.instance.addObserver(_appLifecycleObserver);
}

final _appLifecycleObserver = _AppLifecycleObserver();

// 监听APP前台切换
class _AppLifecycleObserver extends WidgetsBindingObserver {
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      _checkSubscriptionStatus();
    }
  }
}

// 订阅流监听(保留原逻辑,新增触发校验)
void _initSubscriptionListener() {
  InAppPurchase.instance.purchaseStream.listen((event) {
    for (var element in event) {
      if (element.status == PurchaseStatus.purchased) {
        // 购买成功后立即校验最新状态
        _checkSubscriptionStatus();
      }
    }
  });
}

// 主动校验订阅状态
Future<void> _checkSubscriptionStatus() async {
  final QueryPurchaseDetailsResponse response = await InAppPurchase.instance.queryPastPurchases();
  if (response.error != null) {
    // 处理查询错误,比如网络问题
    return;
  }

  for (var purchase in response.pastPurchases) {
    // 只处理你的订阅产品,且状态有效
    if (purchase.productID == "你的订阅产品ID" && purchase.status == PurchaseStatus.purchased) {
      // 解析收据获取真实过期日期(禁止直接加30天,必须从收据取)
      final receiptData = await _parseSubscriptionReceipt(purchase.transactionReceipt!);
      if (receiptData != null && receiptData.expirationDate.isAfter(widget.userModel.expirationDate)) {
        widget.userModel.expirationDate = receiptData.expirationDate;
        await FirebaseFirestore.instance.collection('users').doc(widget.firebaseUser.uid).update(widget.userModel.toMap());
      }
    }
  }
}

// 解析订阅收据(建议移到Firebase Cloud Functions做服务器端校验,更安全)
Future<ReceiptData?> _parseSubscriptionReceipt(String receipt) async {
  // iOS:调用苹果收据验证API;Android:调用谷歌验证API
  // 此处为示例,实际需替换为真实校验逻辑
  // 注意:禁止在客户端硬编码过期时长,必须从官方接口获取真实时间
  final expirationDate = DateTime.now().add(const Duration(days: 30));
  return ReceiptData(expirationDate: expirationDate);
}

class ReceiptData {
  final DateTime expirationDate;
  ReceiptData({required this.expirationDate});
}

@override
void dispose() {
  WidgetsBinding.instance.removeObserver(_appLifecycleObserver);
  super.dispose();
}

二、服务器端监听订阅通知(推荐,更可靠)

客户端校验存在被篡改的风险,建议用Firebase Cloud Functions接收苹果/谷歌的订阅通知,在云端验证并更新用户数据,即使APP未打开也能同步状态。

示例:苹果Server-to-Server通知处理(Cloud Function)

const functions = require("firebase-functions");
const admin = require("firebase-admin");
const axios = require("axios");

admin.initializeApp();

exports.handleAppleSubscription = functions.https.onRequest(async (req, res) => {
  const notification = req.body;
  const userId = notification.app_account_token; // 需在购买时将用户UID传给苹果作为标识
  const receipt = notification.latest_receipt;

  // 验证收据到苹果官方服务器(沙箱环境替换为sandbox地址)
  const verifyUrl = "https://buy.itunes.apple.com/verifyReceipt";
  const verifyRes = await axios.post(verifyUrl, {
    receipt_data: receipt,
    password: "你的苹果共享密钥"
  });

  if (verifyRes.data.status === 0) {
    const latestInfo = verifyRes.data.latest_receipt_info[0];
    const expirationDate = new Date(parseInt(latestInfo.expires_date_ms));
    // 更新Firebase用户过期日期
    await admin.firestore().collection("users").doc(userId).update({
      expirationDate: expirationDate
    });
  }

  res.status(200).send("OK");
});

配置步骤

  1. 在苹果开发者后台配置Server-to-Server通知地址(指向你的Cloud Function URL)
  2. 在谷歌开发者后台配置实时开发者通知,同理处理订阅更新事件
  3. 购买时将用户UID作为app_account_token传给苹果/谷歌,用于关联用户

关键注意事项

  • 禁止在客户端直接计算过期日期,必须从官方收据或通知中获取真实时间(避免套餐时长变更、优惠订阅等场景出错)
  • 服务器端校验是安全底线,防止客户端篡改订阅状态
  • 额外处理订阅取消、退款、过期等事件,更新用户的订阅状态标记

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 13:56:04