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

如何使用flutter_inapp_purchase校验订阅状态并获取订阅到期时间

Flutter iOS端订阅有效期校验解决方案

现有逻辑问题

你当前的iOS端校验逻辑存在两个核心问题:

  • getPurchaseHistory返回的是用户所有历史订阅交易,哪怕订阅过期后这些记录仍然存在,只判断交易状态为purchased/restored会把过期订阅误判为有效
  • purchaseDate是用户首次发起订阅交易的时间,不会随续费更新,自然一直显示历史测试的日期,完全不能用来判断当前订阅是否有效

实现方案

订阅有效期的核心校验逻辑必须依赖苹果/谷歌返回的订阅到期时间,不能仅靠本地交易状态判断,你可以选择服务端校验(更安全,推荐上线使用)或客户端临时校验两种方案:

核心逻辑修改

1. 新增收据校验方法获取到期时间

import 'dart:convert';
import 'package:http/http.dart' as http; // 需额外添加http依赖

class PaymentService {
  // 你在App Store Connect中生成的App专用共享密钥
  final String _iosSharedSecret = "替换为你的共享密钥";
  
  /// 校验收据返回订阅到期时间戳(单位:毫秒),过期返回0,校验失败返回-1
  Future<int> getSubscriptionExpiryTime(PurchasedItem purchase) async {
    if (Platform.isIOS) {
      // iOS端走苹果收据校验接口
      final receipt = purchase.transactionReceipt;
      if (receipt == null) return -1;
      
      // 测试环境用sandbox地址,上线切换为生产地址即可
      const verifyUrl = "https://sandbox.itunes.apple.com/verifyReceipt";
      // 生产环境地址:https://buy.itunes.apple.com/verifyReceipt
      
      final response = await http.post(
        Uri.parse(verifyUrl),
        body: jsonEncode({
          "receipt-data": receipt,
          "password": _iosSharedSecret,
          "exclude-old-transactions": true
        })
      );
      
      if (response.statusCode == 200) {
        final resData = jsonDecode(response.body);
        if (resData['status'] == 0) {
          // 取最新的订阅到期时间
          final latestReceiptInfo = resData['latest_receipt_info'] as List;
          if (latestReceiptInfo.isNotEmpty) {
            final latestExpiry = int.parse(latestReceiptInfo.last['expires_date_ms']);
            return latestExpiry;
          }
        }
      }
      return -1;
    } else if (Platform.isAndroid) {
      // Android端可直接读取expiryTimeMillis字段,也可走Google Play开发者API校验更安全
      return purchase.expiryTimeMillis ?? 0;
    }
    return -1;
  }
}

2. 修改有效订阅判断逻辑

Future<PurchasedItem?> getActiveSubscription() async {
  int currentTime = DateTime.now().millisecondsSinceEpoch;
  if (Platform.isIOS) {
    var history = await FlutterInappPurchase.instance.getPurchaseHistory();
    // 先按交易时间倒序,优先校验最新的交易记录
    history.sort((a,b) => (b.transactionDate?.millisecondsSinceEpoch ?? 0).compareTo(a.transactionDate?.millisecondsSinceEpoch ?? 0));
    
    for (var purchase in history) {
      if([TransactionState.purchased, TransactionState.restored].contains(purchase.transactionStateIOS)) {
        int expiryTime = await getSubscriptionExpiryTime(purchase);
        // 到期时间大于当前时间才是有效订阅
        if (expiryTime > currentTime) {
          return purchase;
        }
      }
    }
    return null;
  } else if (Platform.isAndroid) {
    var purchases = await FlutterInappPurchase.instance.getAvailablePurchases();
    for (var purchase in purchases) {
      if (purchase.productId == _productId) {
        int expiryTime = await getSubscriptionExpiryTime(purchase);
        if (expiryTime > currentTime) {
          return purchase;
        }
      }
    }
    return null;
  }
  throw PlatformException(code: Platform.operatingSystem, message: "platform not supported");
}

3. 展示订阅到期时间

拿到有效订阅后,调用getSubscriptionExpiryTime获取到期时间戳,转成DateTime格式直接展示即可:

// 示例:获取到期时间展示文本
PurchasedItem? activeSub = await PaymentService.instance.getActiveSubscription();
if (activeSub != null) {
  int expiryMs = await PaymentService.instance.getSubscriptionExpiryTime(activeSub);
  DateTime expiryDate = DateTime.fromMillisecondsSinceEpoch(expiryMs);
  String showText = "订阅到期时间:${expiryDate.year}/${expiryDate.month}/${expiryDate.day}";
}

上线注意事项

上面的客户端直接校验的方案仅适合测试使用,正式上线推荐:

  • 客户端把收据/购买令牌传给自己的后端服务
  • 由后端服务调用苹果、谷歌的校验接口完成验证,再返回到期时间给客户端,避免客户端被破解篡改校验结果

内容的提问来源于stack exchange,提问作者Darius Buhai

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 17:06:02