Flutter中无transaction_id时关联App Store服务器通知与用户的方案咨询
一、交易与用户映射的可靠方案
核心思路:基于originalTransactionId建立持久映射
Apple订阅的originalTransactionId是订阅生命周期内的唯一标识——首次订阅生成后,后续续订、恢复、取消等操作都会沿用这个ID。服务器端只需在用户首次购买成功时,将originalTransactionId与用户UUID绑定存储,后续所有Apple服务器通知(续订、取消、过期等)都会携带这个ID,直接通过它即可关联到对应用户。
操作步骤:
- 用户购买成功后,Flutter端将
purchaseID(即transactionId)、originalTransactionId、user_id同步到服务器; - 服务器存储映射关系:
originalTransactionId → user_id,可额外存储transactionId → user_id作为冗余; - 收到Apple服务器通知时,解析通知的
signedPayload,提取originalTransactionId,直接查询映射表获取用户。
fallback方案:通过API从收据中提取applicationUserName
若因特殊情况未存储originalTransactionId,可通过通知中的transactionId调用App Store Server API获取交易详情,从返回的收据数据中提取applicationUsername字段(即你传入的用户UUID),再关联到用户。
二、替代废弃verifyReceipt的收据验证方案
Apple官方推荐App Store Server API作为verifyReceipt的替代方案,该API支持服务器到服务器的安全通信,提供订阅状态查询、交易详情获取等功能,同时能直接验证数据真实性。
1. 核心验证方式:解析服务器通知的signedPayload
Apple服务器通知的signedPayload是Apple私钥签名的JWT令牌,只需用Apple公钥验证签名,即可确保通知内容未被篡改,无需额外调用验证API。
Node.js实现示例(使用官方库@apple/app-store-server-library):
const { AppStoreServerAPIClient, Environment } = require('@apple/app-store-server-library'); // 初始化客户端,使用你的App Store Connect密钥信息 const client = new AppStoreServerAPIClient({ issuerId: '你的Issuer ID', privateKey: '你的私钥内容', privateKeyId: '你的私钥ID', environment: Environment.PRODUCTION // 测试环境用Environment.SANDBOX }); // 解析并验证Apple服务器通知 async function processAppleNotification(signedPayload) { try { const notification = await client.parseAndValidateNotification(signedPayload); // 提取关键交易信息 const originalTransactionId = notification.data.originalTransactionId; const subscriptionStatus = notification.data.subscriptionStatus; // 通过originalTransactionId查询关联用户 const user = await yourDatabase.query('SELECT * FROM user_subscriptions WHERE original_transaction_id = ?', [originalTransactionId]); // 处理订阅状态更新逻辑(如续订、取消、过期) updateUserSubscriptionStatus(user.id, subscriptionStatus); } catch (error) { console.error('通知验证失败:', error); } }
2. 主动查询订阅状态:使用Get Subscription Status API
如果需要主动验证用户订阅状态(比如用户打开APP时),可调用Get Subscription Status API,传入originalTransactionId或transactionId,获取最新订阅信息,同时可从返回的latestReceipt中提取applicationUsername关联用户。
Node.js示例:
async function getSubscriptionStatus(originalTransactionId) { try { const statusResponse = await client.getSubscriptionStatus(originalTransactionId); const latestTransaction = statusResponse.data[0].latestTransaction; const applicationUsername = latestTransaction.receipt.applicationUsername; // 通过applicationUsername查询用户 const user = await yourDatabase.query('SELECT * FROM users WHERE uuid = ?', [applicationUsername]); return { user, subscriptionStatus: statusResponse.data[0].status }; } catch (error) { console.error('订阅状态查询失败:', error); } }
关键注意事项
- 确保每次发起购买时,
applicationUserName参数都传入正确的用户UUID,Apple会将其存入交易收据; - 服务器需妥善存储App Store Connect的密钥信息(Issuer ID、私钥、私钥ID),避免泄露;
- 开发测试时使用沙盒环境API,上线切换为生产环境。
内容的提问来源于stack exchange,提问作者Nikunj Agrawal

