You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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-v1
  • exp:令牌过期时间(建议设为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 09:50:55