iOS应用Node.js后端:弃用/verifyResponse后的IAP流程实现咨询
替代弃用的/verifyResponse实现iOS收据验证与交易数据同步流程(Node.js)
核心方案概述
Apple已弃用旧版收据验证接口,当前推荐用App Store Server API完成收据验证,同时结合Apple Server Notification(ASN)同步交易全量数据。以下是具体落地步骤:
1. 用App Store Server API验证收据并提取originalTransactionId
第一步:生成JWT认证令牌
调用App Store Server API必须使用开发者账号密钥生成JWT令牌,核心参数如下:
iss:你的开发者账号ID(在App Store Connect「用户和访问」→「密钥」页查看)aud:固定值appstoreconnect-v1exp:令牌过期时间(建议设为1小时内)kid:你的App Store Server API密钥ID
Node.js中用jsonwebtoken库生成令牌的示例:
const jwt = require('jsonwebtoken'); const fs = require('fs'); const privateKey = fs.readFileSync('/path/to/your/private-key.p8'); const token = jwt.sign( {}, privateKey, { algorithm: 'ES256', expiresIn: '1h', issuer: 'YOUR_DEVELOPER_ID', audience: 'appstoreconnect-v1', keyid: 'YOUR_KEY_ID' } );
第二步:调用收据验证接口
通过POST /v1/transactions/verifyReceipt接口验证收据,从返回结果中提取originalTransactionId并存储:
const axios = require('axios'); async function verifyReceipt(receiptData, isSandbox = false) { const baseUrl = isSandbox ? 'https://api.sandbox.itunes.apple.com' : 'https://api.apple.com'; try { const response = await axios.post( `${baseUrl}/v1/transactions/verifyReceipt`, { receiptData: receiptData, // 客户端传入的base64编码收据 password: 'YOUR_APP_SHARED_SECRET' // 自动续期订阅必填 }, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } } ); // 提取目标交易的originalTransactionId(根据业务逻辑选择对应交易项) const targetTransaction = response.data.receipt.inApp[0]; const originalTransactionId = targetTransaction.originalTransactionId; // 存入数据库,同时保留transactionId、productId等关联字段 await saveTransactionToDb({ originalTransactionId, transactionId: targetTransaction.transactionId, productId: targetTransaction.productId, expiresDate: targetTransaction.expiresDate }); return originalTransactionId; } catch (error) { console.error('收据验证失败:', error.response?.data || error.message); throw error; } }
2. 处理Apple Server Notification补全数据
Apple服务器会推送交易状态变更(续订、过期、退款等)的全量数据,通过以下步骤对接:
第一步:搭建Webhook接收通知
用Express搭建HTTPS接口(Apple仅向HTTPS地址推送通知),并验证通知签名:
const express = require('express'); const app = express(); const crypto = require('crypto'); const fs = require('fs'); // 配置获取原始请求体(用于签名验证) app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; } })); // 接收Apple通知的接口 app.post('/apple-notifications', async (req, res) => { // 验证通知签名,防止伪造 if (!verifyNotificationSignature(req)) { return res.sendStatus(403); } const payload = req.body; // 从通知中提取originalTransactionId const originalTransactionId = payload.data.signedTransactionInfo.originalTransactionId; // 关联数据库记录,更新交易状态、续订信息等 await updateTransactionInDb(originalTransactionId, { status: payload.data.signedTransactionInfo.status, autoRenewStatus: payload.data.signedRenewalInfo?.autoRenewStatus, latestReceipt: payload.data.signedTransactionInfo.transactionReceipt }); res.sendStatus(200); }); // 验证通知签名的方法 function verifyNotificationSignature(req) { const signature = req.headers['x-apple-signature']; const signedPayload = req.rawBody; const appleRootCert = fs.readFileSync('/path/to/apple-root-cert.pem'); const verify = crypto.createVerify('sha256'); verify.write(signedPayload); verify.end(); return verify.verify(appleRootCert, signature, 'base64'); } app.listen(443, () => { console.log('Apple通知接收服务已启动'); });
第二步:关联通知与本地数据
通知的signedTransactionInfo字段包含完整交易详情,通过originalTransactionId直接关联数据库中已存储的记录,更新订阅状态、续订周期等关键数据,确保数据一致性。
关键注意事项
- 环境区分:沙箱与生产环境的API地址、通知推送地址不同,需在代码中做分支判断
- JWT令牌刷新:令牌有效期1小时,需定时刷新避免接口调用失败
- 幂等处理:通知可能重复推送,需用
notificationUUID作为幂等键,避免重复更新数据库 - 异常捕获:处理API调用超时、签名验证失败等场景,保证服务稳定性
内容的提问来源于stack exchange,提问作者metacore
相关产品推荐
相关产品推荐

