diff --git a/MeAgent/src/screens/Subscription/SubscriptionScreen.js b/MeAgent/src/screens/Subscription/SubscriptionScreen.js index 21d6c2fe..74419229 100644 --- a/MeAgent/src/screens/Subscription/SubscriptionScreen.js +++ b/MeAgent/src/screens/Subscription/SubscriptionScreen.js @@ -405,24 +405,37 @@ const SubscriptionScreen = () => { const purchase = await iapService.purchaseProduct(productInfo.productId); // 验证收据并激活订阅 + // 注意:purchaseUpdatedListener 也可能触发验证,但后端有防重复机制 if (purchase && purchase.transactionReceipt) { - const result = await iapService.verifyAndActivateSubscription( - purchase.transactionReceipt, - productInfo.productId - ); + // 标记此交易正在处理中(防止监听器重复处理) + if (purchase.transactionId) { + iapService.processingTransactions.add(purchase.transactionId); + } - if (result.success) { - // 完成交易(传入完整的 purchase 对象) - await iapService.finishPurchase(purchase); - - // 刷新用户信息 - await refreshUser(); - - Alert.alert( - '订阅成功', - `您已成功订阅${plan.displayName}!`, - [{ text: '好的', onPress: () => navigation.goBack() }] + try { + const result = await iapService.verifyAndActivateSubscription( + purchase.transactionReceipt, + productInfo.productId ); + + if (result.success) { + // 完成交易(传入完整的 purchase 对象) + await iapService.finishPurchase(purchase); + + // 刷新用户信息 + await refreshUser(); + + Alert.alert( + '订阅成功', + `您已成功订阅${plan.displayName}!`, + [{ text: '好的', onPress: () => navigation.goBack() }] + ); + } + } finally { + // 移除处理中标记 + if (purchase.transactionId) { + iapService.processingTransactions.delete(purchase.transactionId); + } } } } catch (error) { diff --git a/MeAgent/src/services/iapService.js b/MeAgent/src/services/iapService.js index 52a2913c..37faeb59 100644 --- a/MeAgent/src/services/iapService.js +++ b/MeAgent/src/services/iapService.js @@ -110,6 +110,8 @@ class IAPService { this.subscriptions = []; this.purchaseUpdateSubscription = null; this.purchaseErrorSubscription = null; + // 用于防止重复验证的标记 + this.processingTransactions = new Set(); } /** @@ -170,11 +172,26 @@ class IAPService { } // 购买成功/更新监听器 + // 注意:这个监听器主要用于处理后台恢复的购买或 App 重启后的未完成交易 + // 正常购买流程由 SubscriptionScreen 的 handleSubscribe 处理 this.purchaseUpdateSubscription = purchaseUpdatedListener(async (purchase) => { console.log('[IAPService] 购买更新:', purchase); + const transactionId = purchase.transactionId; + + // 检查是否已经在处理中(防止重复验证) + if (transactionId && this.processingTransactions.has(transactionId)) { + console.log('[IAPService] 交易已在处理中,跳过:', transactionId); + return; + } + if (purchase.transactionReceipt) { try { + // 标记为处理中 + if (transactionId) { + this.processingTransactions.add(transactionId); + } + // 验证收据 const verifyResult = await this.verifyAndActivateSubscription( purchase.transactionReceipt, @@ -188,6 +205,11 @@ class IAPService { } } catch (error) { console.error('[IAPService] 处理购买失败:', error); + } finally { + // 移除处理中标记 + if (transactionId) { + this.processingTransactions.delete(transactionId); + } } } }); diff --git a/app.py b/app.py index 2e0fae7e..fc0e8a9e 100755 --- a/app.py +++ b/app.py @@ -3995,6 +3995,9 @@ def apple_webhook(): 文档: https://developer.apple.com/documentation/appstoreservernotifications """ + import base64 + import json as json_lib + try: # 获取请求数据 data = request.get_json() @@ -4011,9 +4014,6 @@ def apple_webhook(): # TODO: 实现 JWT 签名验证(使用苹果的公钥) # 目前先解码 payload 部分(不验证签名,生产环境应验证) try: - import base64 - import json as json_lib - # JWT 格式: header.payload.signature parts = signed_payload.split('.') if len(parts) >= 2: