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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 20:40:31