Flutter中Apple订阅服务器端验证:如何区分新旧购买?
区分Flutter订阅新购买与已验证旧购买的解决方案
针对Apple每次启动应用推送所有历史订阅交易的问题,核心思路是追踪已处理的交易ID,结合本地或后端存储实现过滤,以下是具体可行方案:
1. 本地存储记录已处理交易ID
利用SharedPreferences或本地数据库(如Hive)保存所有已验证过的交易ID,每次收到交易时先检查是否已处理,避免重复操作:
核心代码示例
import 'package:shared_preferences/shared_preferences.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; void _listenToPurchaseUpdated(List<PurchaseDetails> purchases) { for (var purchase in purchases) { if (purchase.status == PurchaseStatus.purchased || purchase.status == PurchaseStatus.restored) { // 先校验交易是否已处理 _checkTransactionProcessed(purchase.transactionId!).then((isProcessed) async { if (!isProcessed) { // 调用后端验证购买有效性 final isValid = await _verifyPurchase(purchase); if (isValid) { // 更新用户订阅状态等业务逻辑 _updateUserSubscription(purchase); // 标记该交易为已处理 _markTransactionProcessed(purchase.transactionId!); } } // 关键:务必调用completePurchase,告知Apple交易已处理,避免重复推送 await InAppPurchase.instance.completePurchase(purchase); }); } } } // 检查交易ID是否已存在于本地存储 Future<bool> _checkTransactionProcessed(String transactionId) async { final prefs = await SharedPreferences.getInstance(); final processedIds = prefs.getStringList('processed_sub_transactions') ?? []; return processedIds.contains(transactionId); } // 将已处理的交易ID存入本地 Future<void> _markTransactionProcessed(String transactionId) async { final prefs = await SharedPreferences.getInstance(); final processedIds = prefs.getStringList('processed_sub_transactions') ?? []; if (!processedIds.contains(transactionId)) { processedIds.add(transactionId); await prefs.setStringList('processed_sub_transactions', processedIds); } }
2. 后端维护用户交易记录(更可靠)
由于本身依赖后端验证购买,可直接在后端存储用户所有已验证的交易ID,移动端每次收到交易时先调用后端接口查询是否已处理:
核心逻辑
- 后端新增接口:接收
transactionId和userId,返回该交易是否已验证过 - 移动端收到交易后,先调用此接口,未处理的再执行验证流程
- 验证通过后,后端自动将该
transactionId关联到用户记录中
这种方案不受设备更换影响,数据一致性更强,适合多设备场景。
关键注意事项
- 必须调用
completePurchase:无论交易是否需要处理,完成后都要调用此方法,否则Apple会持续推送该交易 - 清理冗余记录:本地存储可定期清理超过一定期限(如1年)的交易ID,避免存储膨胀
- 处理恢复购买:
PurchaseStatus.restored状态的交易同样需要用交易ID过滤,避免重复恢复
内容的提问来源于stack exchange,提问作者Arash
相关产品推荐
相关产品推荐

