如何使用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
相关产品推荐
相关产品推荐

