Flutter中in_app_purchase无法检测订阅续订问题求助
问题根源分析
transactionDate字段不适合判断续订:Google Play订阅中,transactionDate通常对应初始购买的日期,而非每次续订的交易日期。即使续订成功,该字段也不会更新,导致你无法通过它检测到续订行为。restorePurchases()的局限性:该方法仅返回历史购买记录的快照,不会主动推送实时续订事件;且插件可能将同一订阅的多次续订合并为一条记录,无法直接区分。purchaseStream触发限制:订阅续订发生时,若应用处于后台或未运行状态,Google Play不会主动通过purchaseStream推送事件,导致应用无法实时感知。
解决方案
1. 改用订阅到期时间判断续订
每次续订都会延长订阅的到期时间,这是检测续订最可靠的依据。需要从订阅的服务器验证数据中解析出到期时间,替代transactionDate。
修改_listenToPurchaseUpdated方法:
Future<void> _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) async { for (final PurchaseDetails purchaseDetails in purchaseDetailsList) { if (purchaseDetails.status == PurchaseStatus.purchased || purchaseDetails.status == PurchaseStatus.restored) { if (!mounted) return; // 解析Google Play服务器验证数据(JSON格式) final Map<String, dynamic> verificationData = jsonDecode(purchaseDetails.verificationData.serverVerificationData); final List<dynamic> receiptInfos = verificationData['latest_receipt_info']; final Map<String, dynamic> latestReceipt = receiptInfos.last; final String expiryDateStr = latestReceipt['expiry_date']; final int expiryTimestamp = int.parse(expiryDateStr); final DateTime currentExpiryDate = DateTime.fromMillisecondsSinceEpoch(expiryTimestamp); // 获取Firestore存储的最后到期时间戳 final String lastExpiryTimestamp = await SubscriptionService().getLastExpiryTimestamp(); if (lastExpiryTimestamp.isEmpty) { // 首次订阅,直接更新 await deliverProduct(purchaseDetails); await SubscriptionService().updateLastExpiryTimestamp(expiryDateStr); } else { final DateTime lastExpiryDate = DateTime.fromMillisecondsSinceEpoch(int.parse(lastExpiryTimestamp)); if (currentExpiryDate.isAfter(lastExpiryDate)) { // 到期时间延长,判定为续订成功 loggy.info("订阅已续订,更新用户权限"); await deliverProduct(purchaseDetails); await SubscriptionService().updateLastExpiryTimestamp(expiryDateStr); } } } // 完成购买流程 if (purchaseDetails.pendingCompletePurchase) { await _inAppPurchase.completePurchase(purchaseDetails); } } }
2. 主动定期查询订阅状态
由于purchaseStream无法保证实时接收续订事件,建议监听应用生命周期,在应用回到前台时主动查询订阅状态:
class YourScreen extends StatefulWidget { @override _YourScreenState createState() => _YourScreenState(); } class _YourScreenState extends State<YourScreen> with WidgetsBindingObserver { late StreamSubscription<List<PurchaseDetails>> _subscription; final InAppPurchase _inAppPurchase = InAppPurchase.instance; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _setupPurchaseStream(); _inAppPurchase.restorePurchases(); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); _subscription.cancel(); super.dispose(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); // 应用从后台回到前台时,主动查询订阅状态 if (state == AppLifecycleState.resumed) { _inAppPurchase.restorePurchases(); } } void _setupPurchaseStream() { final Stream<List<PurchaseDetails>> purchaseUpdated = _inAppPurchase.purchaseStream; _subscription = purchaseUpdated.listen( _listenToPurchaseUpdated, onDone: () => _subscription.cancel(), onError: (error) => loggy.error("购买流错误: $error"), ); } // 你的_listenToPurchaseUpdated方法... }
3. 配置实时订阅通知(可选但推荐)
为实现真正的实时检测,可配置Google Play实时开发者通知,通过Firebase Cloud Messaging(FCM)接收订阅状态变更推送:
- 在Google Play控制台开启实时开发者通知,配置FCM服务器密钥。
- 应用中集成FCM,监听
SUBSCRIPTION_RENEWED等订阅事件。 - 收到通知后,调用
restorePurchases()或直接查询订阅状态,更新用户权限。
4. 修复代码细节问题
- 方法名拼写错误:
hendleError→handleError。 - 为
purchaseDetailsList指定类型List<PurchaseDetails>,避免类型不安全。
测试注意事项
- Google Play测试订阅的最短续订周期为5分钟(测试模式),需等待足够时间观察效果。
- 确保测试账号已添加到Google Play控制台的许可测试列表中。
内容的提问来源于stack exchange,提问作者mr.Penguin
相关产品推荐
相关产品推荐

