Flutter Firebase中如何实现应用内购买的订阅自动续费功能
处理Flutter应用内订阅自动续费与Firebase实时数据库更新
一、基础配置准备
- 确保集成官方
in_app_purchase包,完成iOS App Store Connect、Android Google Play Console的订阅项目配置(自动续费默认开启,需确认订阅周期、价格等设置)。 - 配置Firebase实时数据库规则,限制用户仅能读写自身订阅数据:
{ "rules": { "users": { "$uid": { "subscription": { ".read": "$uid === auth.uid", ".write": "$uid === auth.uid" } } } } }
二、客户端监听订阅状态变化
利用in_app_purchase的purchaseStream监听所有购买/续费事件,包括自动续费触发的状态更新:
import 'package:in_app_purchase/in_app_purchase.dart'; import 'package:firebase_database/firebase_database.dart'; import 'dart:io'; class SubscriptionHandler { final InAppPurchase _iap = InAppPurchase.instance; StreamSubscription<List<PurchaseDetails>>? _purchaseStream; // 初始化订阅监听 void initSubscriptionListener(String userId) { _purchaseStream = _iap.purchaseStream.listen( (purchases) => _handlePurchaseEvents(purchases, userId), onError: (err) => print('订阅监听错误: $err'), onDone: () => _purchaseStream?.cancel(), ); } // 处理购买/续费事件 void _handlePurchaseEvents(List<PurchaseDetails> purchases, String userId) { for (var purchase in purchases) { if (purchase.status == PurchaseStatus.purchased || purchase.status == PurchaseStatus.restored) { _verifyAndUpdateSubscription(purchase, userId); } } } // 验证凭证并更新数据库 Future<void> _verifyAndUpdateSubscription(PurchaseDetails purchase, String userId) async { String? receipt; if (Platform.isIOS) { receipt = purchase.verificationData.localVerificationData; } else if (Platform.isAndroid) { receipt = purchase.verificationData.serverVerificationData; } if (receipt == null) return; // 调用Firebase云函数验证凭证(避免客户端篡改,必须做) final verifyCallable = FirebaseFunctions.instance.httpsCallable('verifySubscription'); final result = await verifyCallable.call({ 'receipt': receipt, 'userId': userId, 'platform': Platform.isIOS ? 'ios' : 'android' }); if (result.data != null) { DateTime newExpiry = DateTime.parse(result.data['expiryDate']); await FirebaseDatabase.instance.ref('users/$userId/subscription').update({ 'expiryDate': newExpiry.toIso8601String(), 'isActive': true, 'subscriptionId': purchase.productID, 'purchaseToken': purchase.verificationData.serverVerificationData }); } } }
三、后端处理离线自动续费
自动续费可能在应用未启动时触发,必须通过后端机制保障状态同步:
方案1:Firebase云函数定时任务
每天执行一次,检查即将过期的订阅,调用平台API验证状态并更新数据库:
const functions = require("firebase-functions"); const admin = require("firebase-admin"); const { google } = require("googleapis"); admin.initializeApp(); exports.checkExpiringSubscriptions = functions.pubsub.schedule('every 24 hours').onRun(async () => { const usersRef = admin.database().ref('users'); const userSnapshot = await usersRef.once('value'); userSnapshot.forEach(async (userSnap) => { const userId = userSnap.key; const subData = userSnap.val()?.subscription; if (!subData) return; const currentExpiry = new Date(subData.expiryDate); // 提前3天检查即将过期的订阅 if (currentExpiry.getTime() - Date.now() < 3 * 24 * 60 * 60 * 1000) { let isActive = false; let newExpiry; if (subData.platform === 'android') { // 调用Google Play开发者API验证订阅 const auth = new google.auth.GoogleAuth({ keyFile: 'service-account.json', scopes: ['https://www.googleapis.com/auth/androidpublisher'] }); const androidPublisher = google.androidpublisher({ version: 'v3', auth }); const verifyResult = await androidPublisher.purchases.subscriptions.get({ packageName: 'com.your.app.package', subscriptionId: subData.subscriptionId, token: subData.purchaseToken }); isActive = verifyResult.data.expiryTimeMillis > Date.now(); newExpiry = new Date(verifyResult.data.expiryTimeMillis); } else if (subData.platform === 'ios') { // 调用Apple App Store API验证订阅(需配置Apple开发者密钥) // 实现iOS订阅验证逻辑 } if (isActive) { await usersRef.child(userId).child('subscription').update({ 'expiryDate': newExpiry.toISOString(), 'isActive': true }); } else { await usersRef.child(userId).child('subscription').update({ 'isActive': false }); } } }); });
方案2:监听平台服务器通知
- iOS:在App Store Connect配置服务器通知URL,当订阅续费、过期时,App Store会推送事件到你的Firebase HTTP云函数,直接更新数据库。
- Android:在Google Play Console开启实时开发者通知,同样通过后端接收事件并同步状态。
四、客户端启动状态同步
每次应用启动时,主动检查订阅状态,避免本地与数据库不一致:
Future<void> syncSubscriptionOnLaunch(String userId) async { final subRef = FirebaseDatabase.instance.ref('users/$userId/subscription'); final snapshot = await subRef.get(); if (snapshot.exists) { DateTime expiry = DateTime.parse(snapshot.child('expiryDate').value.toString()); if (expiry.isBefore(DateTime.now())) { // 尝试恢复购买,同步最新续费状态 await InAppPurchase.instance.restorePurchases(); } } else { await InAppPurchase.instance.restorePurchases(); } }
内容的提问来源于stack exchange,提问作者Sumita Naiya
相关产品推荐
相关产品推荐

