Flutter中使用in_app_purchase 3.1.7检查用户有效订阅
解决in_app_purchase 3.1.7版本检查有效订阅的问题
你的现有代码存在几个核心问题:
- 仅监听实时购买流,但App启动时不会自动返回历史订阅记录,必须主动触发恢复购买操作才能获取过往订阅信息
- 用
pendingCompletePurchase判断订阅有效性完全错误,这个字段只是标记是否需要调用completePurchase()完成交易,和订阅是否有效无关 checkSubscriptionStatus里的setState写法无效,没有实际更新状态
正确实现步骤
1. 主动恢复历史购买记录
App启动时必须调用恢复购买接口,才能拉取用户之前的订阅记录。恢复操作的结果会通过purchaseStream返回,所以要确保提前监听了该流。
2. 正确判断订阅有效性
针对订阅类型的PurchaseDetails,先通过productID确认是目标订阅,再通过解析收据获取订阅的过期时间,对比当前时间判断是否有效。注意iOS和Android的收据格式不同,建议后端验证避免客户端篡改风险。
3. 完整代码示例
import 'package:in_app_purchase/in_app_purchase.dart'; import 'package:flutter/material.dart'; import 'dart:convert'; class SubscriptionStatusPage extends StatefulWidget { const SubscriptionStatusPage({Key? key}) : super(key: key); @override State<SubscriptionStatusPage> createState() => _SubscriptionStatusPageState(); } class _SubscriptionStatusPageState extends State<SubscriptionStatusPage> { final InAppPurchase _inAppPurchase = InAppPurchase.instance; bool _isPremiumSubscribed = false; late StreamSubscription<List<PurchaseDetails>> _purchaseStreamSubscription; @override void initState() { super.initState(); // 监听所有购买/恢复事件 _purchaseStreamSubscription = _inAppPurchase.purchaseStream.listen( _handlePurchaseUpdates, onError: (err) => print('购买流异常: $err'), ); // App启动时自动恢复历史购买 _restoreExistingPurchases(); } @override void dispose() { _purchaseStreamSubscription.cancel(); super.dispose(); } // 恢复历史购买记录 Future<void> _restoreExistingPurchases() async { if (await _inAppPurchase.isAvailable()) { await _inAppPurchase.restorePurchases(); } } // 处理购买/恢复的状态更新 void _handlePurchaseUpdates(List<PurchaseDetails> purchaseDetailsList) { for (final purchase in purchaseDetailsList) { switch (purchase.status) { case PurchaseStatus.purchased: case PurchaseStatus.restored: // 必须完成交易,否则iOS会重复触发该事件 if (purchase.pendingCompletePurchase) { _inAppPurchase.completePurchase(purchase); } // 验证目标订阅是否有效 _checkSubscriptionValidity(purchase); break; case PurchaseStatus.error: print('购买失败: ${purchase.error?.message}'); break; case PurchaseStatus.canceled: // 用户取消购买,更新订阅状态 if (purchase.productID == 'premium') { setState(() => _isPremiumSubscribed = false); } break; default: break; } } } // 验证订阅有效性 void _checkSubscriptionValidity(PurchaseDetails purchase) { if (purchase.productID != 'premium') return; final verificationData = purchase.verificationData; if (verificationData.source == VerificationSource.googlePlay) { // Android端解析Google Play收据 try { final receiptJson = jsonDecode(verificationData.localVerificationData); final expiryTimeMillis = int.parse( receiptJson['purchaseDetails']['expiryTimeMillis'].toString() ); final now = DateTime.now().millisecondsSinceEpoch; setState(() { _isPremiumSubscribed = expiryTimeMillis > now; }); } catch (e) { print('Android收据解析失败: $e'); setState(() => _isPremiumSubscribed = false); } } else if (verificationData.source == VerificationSource.appStore) { // iOS端建议将Base64收据发送到后端,由后端调用App Store API验证 // 此处为示例逻辑,实际需替换为后端验证结果 setState(() => _isPremiumSubscribed = true); } } // 查询订阅商品信息(保留你的原有逻辑) Future<void> loadSubscriptionProducts() async { final productIds = {'premium'}; final response = await _inAppPurchase.queryProductDetails(productIds); print('订阅商品列表: ${response.productDetails}'); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Text( _isPremiumSubscribed ? '已开通Premium订阅' : '未开通Premium订阅', style: const TextStyle(fontSize: 18), ), ), ); } }
关键注意事项
- 必须调用
completePurchase():所有pendingCompletePurchase为true的交易都要调用该方法完成,否则iOS会在App重启后重复触发该购买事件。 - 优先后端验证收据:客户端解析收据存在被篡改的风险,建议将
verificationData发送到你的后端,由后端调用官方API验证订阅状态。 - 定期重新验证:订阅过期后平台不会主动通知客户端,建议在App启动、订阅到期前1天等节点重新验证订阅状态。
内容的提问来源于stack exchange,提问作者nologic
相关产品推荐
相关产品推荐

