Flutter同一设备多用户使用相同productID订阅报错问题
解决同一设备不同用户重复购买同ID订阅的问题
核心原因
切换应用账号后,in_app_purchase 包残留了上一用户的购买缓存,或与应用商店的用户会话绑定未解除,导致校验时判定当前设备的商店用户已拥有该订阅,无法重复发起购买请求。
具体解决方案
1. 登出时重置购买状态缓存
在用户登出逻辑中,调用包内方法清除本地购买缓存,同时清理自身业务存储的订阅记录:
import 'package:in_app_purchase/in_app_purchase.dart'; import 'package:shared_preferences/shared_preferences.dart'; void handleUserLogout() { // 重置in_app_purchase客户端状态 InAppPurchase.instance.restorePurchases(); // 清除本地存储的订阅相关数据(若自行维护) final prefs = await SharedPreferences.getInstance(); prefs.remove('user_subscription_status'); }
restorePurchases()会触发应用商店的购买恢复流程,同时重置本地缓存,确保新用户进入时无残留数据干扰。
2. Android端:重置BillingClient连接
Android需在登出时断开并重建BillingClient连接,清除商店会话缓存。通过MethodChannel调用原生逻辑:
// Android原生代码示例 fun resetBillingClient(context: Context) { val billingClient = BillingClient.newBuilder(context) .enablePendingPurchases() .setListener(purchaseUpdateListener) .build() if (billingClient.isReady) { billingClient.endConnection() } billingClient.startConnection(object : BillingClientStateListener { override fun onBillingSetupFinished(result: BillingResult) { // 连接完成后等待新用户操作 } override fun onBillingServiceDisconnected() { // 处理断开重连逻辑 } }) }
3. iOS端:重置SKPaymentQueue观察者
iOS需在登出时移除旧的支付队列观察者,新用户登录后重新添加,避免旧观察者持有上一用户状态:
// iOS原生代码示例 func resetPaymentQueue() { // 移除旧观察者 SKPaymentQueue.default().removeTransactionObserver(existingObserver) // 新用户登录后添加新观察者 SKPaymentQueue.default().add(newObserver) }
4. 购买前通过后端校验订阅状态
发起购买前,调用后端接口校验当前应用账号的订阅状态,完全依赖业务后端数据而非本地缓存:
Future<bool> checkUserSubscription(String productId, String userId) async { final response = await http.get( Uri.parse("${yourBackendDomain}/api/check-sub?userId=$userId&productId=$productId") ); return response.statusCode == 200 && response.body == "true"; } void startPurchase(String productId, String userId) async { final hasSub = await checkUserSubscription(productId, userId); if (!hasSub) { // 获取商品详情并发起购买 final productDetails = await fetchProductDetails(productId); final purchaseParam = PurchaseParam(productDetails: productDetails); InAppPurchase.instance.buyNonConsumable(purchaseParam: purchaseParam); } }
5. 处理"已购买"类错误回调
针对Android的ITEM_ALREADY_OWNED、iOS的SKErrorPaymentAlreadyOwned错误,触发购买恢复并提交凭证给后端验证归属:
void listenPurchaseStream(String currentUserId) { InAppPurchase.instance.purchaseStream.listen((purchaseList) { for (var purchase in purchaseList) { if (purchase.status == PurchaseStatus.error) { final errorMsg = purchase.error?.message ?? ""; if (errorMsg.contains("ITEM_ALREADY_OWNED") || errorMsg.contains("SKErrorPaymentAlreadyOwned")) { InAppPurchase.instance.restorePurchases(); } } else if (purchase.status == PurchaseStatus.restored) { // 将恢复的凭证提交后端验证是否属于当前用户 submitTokenToBackend( purchase.verificationData.serverVerificationData, currentUserId ); } } }); }
后端验证凭证不属于当前用户时,允许重新发起购买或标记凭证失效。
内容的提问来源于stack exchange,提问作者Sagar
相关产品推荐
相关产品推荐

