Flutter痛点 — Android端的Flutter in_app_purchase:购买成功(收据已收到),但purchaseStream报错——verifyIAP从未被调用
我们在Flutter上使用 in_app_purchase: ^3.2.3 (in_app_purchase_android: 0.4.0+10)。在Android上,当用户完成购买时:
- Google Play处理正确(已收到测试收据邮件、生成订单ID,在沙箱中显示 “Test card, always approves”)
- 应用会短暂显示 “购买未完成。如果您取消,请再试一次。”
- 档位保持不变
- 我们后端的Cloud Function (
verifyIAP) 从未被调用 —— 通过GCP日志确认
在第二次尝试时:“You already own this item” —— 这表明Google Play认为购买已完成但尚未确认。
我们的购买流处理程序:
void _onPurchaseUpdate(List<PurchaseDetails> purchases) {
for (final purchase in purchases) {
if (purchase.productID != _pendingProductId) {
if (purchase.status != PurchaseStatus.pending) {
_iap.completePurchase(purchase);
}
continue;
}
switch (purchase.status) {
case PurchaseStatus.purchased:
case PurchaseStatus.restored:
_handlePurchased(purchase);
case PurchaseStatus.canceled:
_iap.completePurchase(purchase);
_resolvePending(IAPResult.cancelled);
case PurchaseStatus.error:
_iap.completePurchase(purchase);
_resolvePending(IAPResult.storeError);
case PurchaseStatus.pending:
break;
}
}
}
错误信息 “Purchase didn't complete. If you cancelled, just try again.” 对应到 IAPResult.storeError。
问题: 在Android上,完成的Google Play购买是否可能以 PurchaseStatus.error 的形式到达 purchaseStream,而不是 PurchaseStatus.purchased?如果可以,需满足哪些条件?或者是否存在其他机制,在到达我们的处理程序之前就会使流事件被丢弃?
我们已排除的情况:
didChangeAppLifecycleState取消路径受if (Platform.isAndroid) return;保护- 30秒超时未触发(错误很快就出现)
_pendingProductId不匹配是可能的,但不清楚为何在流交付时会为null- iOS使用相同代码也能正常工作
非常感谢任何帮助!
解决方案
我们终于解决了……
两种机制:
1/ didChangeAppLifecycleState 在计费覆盖层被取消时触发(这是你的bug)
Play的计费覆盖层会短暂暂停应用。如果你的生命周期观察者采用iOS风格的取消逻辑,它会在 purchaseStream 传送 purchased 事件之前把 _pendingProductId 清空为null。事件到达时没有匹配项,因此被丢弃。修复:
void didChangeAppLifecycleState(AppLifecycleState state) {
if (Platform.isAndroid) return;
// iOS-only cancellation logic below...
}
2/ 真正的购买可能以 PurchaseStatus.error 的形式到达
itemAlreadyOwned 被传递为 PurchaseStatus.error,并带有一个 空的 productID。如果你的不匹配检查先检查 productID == _pendingProductId,空字符串会导致它失败,事件在到达错误处理程序之前会被静默丢弃。在进入不匹配分支之前,请检查 itemAlreadyOwned +空的productID,然后调用 restorePurchases() —— 未确认的令牌将以携带实际产品ID的 PurchaseStatus.restored 出现。
iOS对这两种情况都不受影响。