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

Flutter in_app_purchase订阅验证与本地存储过期问题咨询

解决Flutter订阅状态校验的API负担与本地一致性问题

针对你用in_app_purchase开发月度订阅应用时遇到的矛盾,这里提供几个平衡API调用频率和状态准确性的实用方案:

1. 本地存储+定时校验的混合策略

用Hive存储三个核心字段,避免每次启动都调用API:

  • isSubscribed: 订阅状态
  • expiryTime: 订阅到期时间戳
  • lastCheckTime: 上次与Google API校验的时间戳

启动时的校验逻辑

Future<void> checkSubscriptionStatus() async {
  final box = Hive.box('subscription_box');
  final isSubscribed = box.get('isSubscribed', defaultValue: false);
  final expiryTime = box.get('expiryTime', defaultValue: 0);
  final lastCheckTime = box.get('lastCheckTime', defaultValue: 0);
  
  final now = DateTime.now().millisecondsSinceEpoch;
  const maxCheckInterval = 24 * 60 * 60 * 1000; // 24小时校验间隔
  
  // 本地订阅未过期且最近24小时校验过,直接用本地状态
  if (isSubscribed && expiryTime > now && now - lastCheckTime < maxCheckInterval) {
    updateAppUI(isSubscribed: true);
    return;
  }
  
  // 触发API校验并同步本地状态
  final purchases = await InAppPurchase.instance.restorePurchases();
  final activeSub = purchases.pastPurchases.firstWhere(
    (p) => p.productID == 'your_monthly_sub_id' && p.status == PurchaseStatus.purchased,
    orElse: () => null,
  );
  
  if (activeSub != null) {
    // 用Google返回的到期时间(若能获取),否则按订阅周期计算
    final expiry = activeSub.transactionDate.add(const Duration(days: 30));
    await box.put('isSubscribed', true);
    await box.put('expiryTime', expiry.millisecondsSinceEpoch);
    updateAppUI(isSubscribed: true);
  } else {
    await box.put('isSubscribed', false);
    updateAppUI(isSubscribed: false);
  }
  await box.put('lastCheckTime', now);
}

2. 监听实时订阅状态变化

利用in_app_purchase的purchaseStream监听订阅状态的实时更新(取消、退款、续订),一旦收到通知立即同步本地存储:

void initPurchaseListener() {
  final box = Hive.box('subscription_box');
  InAppPurchase.instance.purchaseStream.listen((purchaseDetailsList) {
    for (var purchase in purchaseDetailsList) {
      if (purchase.productID != 'your_monthly_sub_id') continue;
      
      switch (purchase.status) {
        case PurchaseStatus.purchased:
        case PurchaseStatus.restored:
          final expiry = purchase.transactionDate.add(const Duration(days: 30));
          box.put('isSubscribed', true);
          box.put('expiryTime', expiry.millisecondsSinceEpoch);
          updateAppUI(isSubscribed: true);
          break;
        case PurchaseStatus.canceled:
        case PurchaseStatus.refunded:
          box.put('isSubscribed', false);
          box.put('expiryTime', DateTime.now().millisecondsSinceEpoch);
          updateAppUI(isSubscribed: false);
          break;
        default:
          break;
      }
    }
  });
}

3. 优化API调用的触发时机

除了启动时的条件校验,还可以在以下场景调用restorePurchases():

  • 用户主动点击「恢复订阅」按钮时
  • 应用从后台切换到前台,且距离上次校验超过1小时时
  • 本地状态显示已过期,用户尝试访问高级功能时

4. 边缘情况处理

  • 离线场景:本地订阅未过期时允许访问高级功能;已过期则提示用户联网校验
  • API与本地不一致:无论何种情况,API返回的状态优先级高于本地存储,强制同步
  • 状态通知延迟:Google Play的退款/取消通知可能有延迟,每日一次的定时校验可覆盖此类情况

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 09:25:56