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"); });
配置步骤
- 在苹果开发者后台配置Server-to-Server通知地址(指向你的Cloud Function URL)
- 在谷歌开发者后台配置实时开发者通知,同理处理订阅更新事件
- 购买时将用户UID作为
app_account_token传给苹果/谷歌,用于关联用户
关键注意事项
- 禁止在客户端直接计算过期日期,必须从官方收据或通知中获取真实时间(避免套餐时长变更、优惠订阅等场景出错)
- 服务器端校验是安全底线,防止客户端篡改订阅状态
- 额外处理订阅取消、退款、过期等事件,更新用户的订阅状态标记
内容的提问来源于stack exchange,提问作者shakky
相关产品推荐
相关产品推荐

