Flutter Firebase订阅自动续费如何通知后端更新过期时间?
解决自动续订时更新Firebase过期日期的方案
一、客户端层面:监听订阅状态+恢复购买(覆盖用户打开APP的场景)
你的现有代码仅处理了首次购买逻辑,自动续订时若用户打开APP,purchaseStream会触发订阅更新事件,但需优化逻辑以适配续订场景:
- 不要手动计算过期时间,从订阅凭证解析官方日期
手动加30天易出错(如不同订阅时长、时区差异),应从PurchaseDetails的交易凭证中提取平台提供的准确过期时间。 - APP启动时主动恢复购买状态
用户未打开APP期间完成续订的,下次启动APP时调用restorePurchases拉取最新订阅状态,同步更新Firebase。
优化后的客户端代码:
@override void initState() { super.initState(); loadPlans(); _listenToPurchaseUpdates(); _restorePurchases(); // APP启动时恢复订阅状态 } void _listenToPurchaseUpdates() { InAppPurchase.instance.purchaseStream.listen((purchaseDetailsList) { _handlePurchaseUpdates(purchaseDetailsList); }); } Future<void> _restorePurchases() async { final bool isAvailable = await InAppPurchase.instance.isAvailable(); if (isAvailable) { await InAppPurchase.instance.restorePurchases(); } } void _handlePurchaseUpdates(List<PurchaseDetails> purchaseDetailsList) { for (var purchase in purchaseDetailsList) { if (purchase.status == PurchaseStatus.purchased || purchase.status == PurchaseStatus.restored) { final receipt = purchase.transactionReceipt; if (receipt != null) { // 解析凭证获取过期时间(实际开发建议将凭证发送到Cloud Function验证后解析,避免客户端篡改) DateTime? expirationDate = _parseExpirationDateFromReceipt(receipt); if (expirationDate != null) { widget.userModel.expirationDate = expirationDate; // 同时存储交易ID/Token,方便服务器通知关联用户 widget.userModel.subscriptionTransactionId = purchase.transactionId; FirebaseFirestore.instance .collection('users') .doc(widget.firebaseUser.uid) .update(widget.userModel.toMap()); } } } } } // 示例:解析苹果订阅收据(Android需解析Google Play的JSON格式凭证) DateTime? _parseExpirationDateFromReceipt(String receipt) { try { final decoded = base64Decode(receipt); final receiptJson = jsonDecode(utf8.decode(decoded)); final latestReceiptInfo = receiptJson['latest_receipt_info']?.last; if (latestReceiptInfo != null) { final expiresDateMs = int.parse(latestReceiptInfo['expires_date_ms']); return DateTime.fromMillisecondsSinceEpoch(expiresDateMs); } } catch (e) { print('解析收据失败: $e'); } return null; }
二、服务器层面:接收应用商店实时通知(覆盖用户未打开APP的场景)
客户端方案存在局限性(用户不打开APP则无法更新),必须结合服务器端监听应用商店的续订通知,这是最可靠的自动更新方式:
1. 配置Firebase Cloud Function接收通知
创建Cloud Function,分别处理苹果App Store和谷歌Google Play的订阅事件:
处理苹果App Store服务器通知的Node.js示例
const functions = require("firebase-functions"); const admin = require("firebase-admin"); admin.initializeApp(); exports.handleAppleSubscriptionNotification = functions.https.onRequest(async (req, res) => { const notification = req.body; const { latest_receipt_info, notification_type } = notification; // 仅处理订阅成功或续订事件 if (['SUBSCRIBED', 'RENEWED'].includes(notification_type) && latest_receipt_info) { const latestReceipt = latest_receipt_info[latest_receipt_info.length - 1]; const transactionId = latestReceipt.transaction_id; // 通过交易ID关联到对应的用户文档 const userSnapshot = await admin.firestore() .collection('users') .where('subscriptionTransactionId', '==', transactionId) .get(); if (!userSnapshot.empty) { const userDoc = userSnapshot.docs[0]; const expiresDateMs = parseInt(latestReceipt.expires_date_ms); const expirationDate = new Date(expiresDateMs); await userDoc.ref.update({ expirationDate: expirationDate.toISOString() }); } } res.status(200).send('OK'); });
处理谷歌Google Play实时通知的Node.js示例
exports.handleGoogleSubscriptionNotification = functions.https.onRequest(async (req, res) => { const notification = req.body; const { subscriptionNotification, purchaseToken } = notification; // 4代表订阅续订事件 if (subscriptionNotification && subscriptionNotification.notificationType === 4) { const { google } = require('googleapis'); const androidpublisher = google.androidpublisher('v3'); const auth = new google.auth.GoogleAuth({ keyFile: 'service-account-key.json', // 你的Google Play服务账号密钥文件 scopes: ['https://www.googleapis.com/auth/androidpublisher'], }); const client = await auth.getClient(); const packageName = 'com.your.app.package'; // 你的APP包名 const subscriptionId = subscriptionNotification.subscriptionId; try { const response = await androidpublisher.purchases.subscriptions.get({ auth: client, packageName: packageName, subscriptionId: subscriptionId, token: purchaseToken, }); const { expiryTimeMillis } = response.data; const expirationDate = new Date(parseInt(expiryTimeMillis)); // 通过purchaseToken关联到对应的用户文档 const userSnapshot = await admin.firestore() .collection('users') .where('subscriptionPurchaseToken', '==', purchaseToken) .get(); if (!userSnapshot.empty) { const userDoc = userSnapshot.docs[0]; await userDoc.ref.update({ expirationDate: expirationDate.toISOString() }); } } catch (e) { functions.logger.error('验证谷歌订阅失败:', e); } } res.status(200).send('OK'); });
2. 配置应用商店的通知URL
- iOS: 在App Store Connect进入APP的「订阅」设置,配置「服务器通知URL」为Cloud Function的HTTPS地址,选择发送「所有事件」。
- Android: 在Google Play Console进入「开发工具」>「API访问」,启用「实时开发者通知」,配置通知URL为对应Cloud Function的HTTPS地址。
三、关键注意事项
- 客户端数据不可信: 所有订阅状态的最终验证必须在服务器端完成,避免客户端篡改数据。
- 关联用户与交易: 客户端购买时需将
transactionId(iOS)或purchaseToken(Android)存储到用户的Firestore文档中,方便服务器通知时匹配用户。 - 处理异常事件: 需同时监听应用商店的订阅过期、取消等通知,及时更新Firebase中的用户状态。
内容的提问来源于stack exchange,提问作者shakky
相关产品推荐
相关产品推荐

