如何通过Flutter in_app_purchase检测Google Play订阅取消状态
如何通过Flutter in_app_purchase检测Google Play订阅取消
核心问题说明
从Google Play商店手动取消订阅时,in_app_purchase的purchaseStream不会主动推送取消事件,因此必须主动查询用户的历史购买记录来同步订阅状态,不能仅依赖购买/恢复时的事件触发。
另外注意:你的代码中调用buyConsumable购买订阅是错误的——订阅属于非消耗品/订阅类产品,应使用buySubscription方法。
解决方案步骤
1. 修正订阅购买方法
将购买逻辑中的buyConsumable替换为buySubscription:
buySubscription(String id) async{ PurchaseParam purchaseParam = PurchaseParam( productDetails: _products.firstWhere((element) => element.id == id)); try { // 订阅需使用buySubscription,而非消耗品购买方法 await _iap.buySubscription(purchaseParam: purchaseParam); } catch (e) { print('Error while buying ${e}'); } }
2. 添加主动查询订阅状态的方法
新增方法用于查询历史购买记录,校验目标订阅的有效性:
Future<void> checkSubscriptionStatus() async { if (!availble) return; // 查询所有历史购买记录 final QueryPurchaseDetailsResponse response = await _iap.queryPastPurchases(); if (response.error != null) { print('查询历史购买失败: ${response.error}'); return; } // 检查目标订阅是否存在且有效 bool isSubscribed = response.pastPurchases.any((purchase) { if (purchase.productID != subscriptionId) return false; // Google Play订阅需检查自动续费状态和到期时间 if (purchase is GooglePlayPurchaseDetails) { // isAutoRenewing为false表示已取消订阅 return purchase.isAutoRenewing && !_isSubscriptionExpired(purchase); } // 其他平台(如iOS)可补充对应逻辑 return purchase.status == PurchaseStatus.purchased || purchase.status == PurchaseStatus.restored; }); // 更新应用内订阅状态 setPurchases(fASubscribed: isSubscribed); notifyListeners(); } // 辅助方法:判断订阅是否过期 bool _isSubscriptionExpired(GooglePlayPurchaseDetails purchase) { if (purchase.subscriptionPeriod == null || purchase.transactionDate == null) return true; final transactionDate = DateTime.parse(purchase.transactionDate!); final period = purchase.subscriptionPeriod!; DateTime expiryDate = transactionDate; switch (period.unit) { case GooglePlaySubscriptionPeriodUnit.day: expiryDate = expiryDate.add(Duration(days: period.value)); break; case GooglePlaySubscriptionPeriodUnit.week: expiryDate = expiryDate.add(Duration(days: period.value * 7)); break; case GooglePlaySubscriptionPeriodUnit.month: expiryDate = expiryDate.add(Duration(days: period.value * 30)); // 简化处理,可使用更精确的日期库优化 break; case GooglePlaySubscriptionPeriodUnit.year: expiryDate = expiryDate.add(Duration(days: period.value * 365)); break; } return expiryDate.isBefore(DateTime.now()); }
3. 在关键时机触发状态检查
- 应用初始化完成后调用:
Future initiazlize() async { final Stream<List<PurchaseDetails>> purchaseUpdated = _iap.purchaseStream; _subscription = purchaseUpdated.listen((purchaseDetailsList) async { _purchases.addAll(purchaseDetailsList); _listenToPurchaseUpdated(purchaseDetailsList); notifyListeners(); }); availble = await _iap.isAvailable(); notifyListeners(); if (availble == true) { print("Is store availble ${availble}"); await _getProducts(); // 初始化时同步订阅状态 await checkSubscriptionStatus(); } }
- 应用从后台回到前台时调用(通过
WidgetsBindingObserver实现):
// 混入WidgetsBindingObserver监听应用生命周期 class YourProvider with ChangeNotifier, WidgetsBindingObserver { @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed && availble) { // 回到前台时同步订阅状态 checkSubscriptionStatus(); } } // 初始化时注册观察者 Future initiazlize() async { WidgetsBinding.instance.addObserver(this); // ... 原有初始化逻辑 } // 销毁时移除观察者 @override void dispose() { WidgetsBinding.instance.removeObserver(this); _subscription?.cancel(); super.dispose(); } }
4. 优化购买事件监听逻辑
在购买状态更新后,主动同步订阅状态:
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) { purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async { switch (purchaseDetails.status) { case PurchaseStatus.pending: break; case PurchaseStatus.purchased: case PurchaseStatus.restored: if (purchaseDetails.productID == subscriptionId) { setPurchases(fASubscribed: true); } break; case PurchaseStatus.error: print(purchaseDetails.error); break; default: break; } if (purchaseDetails.pendingCompletePurchase) { await _iap.completePurchase(purchaseDetails); } // 每次购买事件后同步最新状态 await checkSubscriptionStatus(); }); }
关键注意事项
- Google Play订阅取消状态同步存在延迟(通常几分钟),测试时需等待状态更新后验证。
- 对于已过期的订阅,即使未主动取消,也需通过到期时间判断有效性。
- 确保AndroidManifest配置了正确的Billing权限,且应用已关联Google Play开发者后台的订阅产品。
内容的提问来源于stack exchange,提问作者Mamshad Tanik
相关产品推荐
相关产品推荐

