Skip to content
LIU的开发日志
Go back

Cloudflare Workers + Apple IAP:一场跨越“三大天坑”的硬核排雷实录

Edit page

解决 Cloudflare Workers 报错 "id-ecPublicKey" 的终极指南。包含 Apple IAP (StoreKit 2) 双环境验证、Xcode 本地调试豁免及 jose 对接完整 Swift/TypeScript 代码。

Cloudflare Workers + Apple IAP:一场跨越“三大天坑”的硬核排雷实录

⚠️ 写在前面:如果你正准备在 Cloudflare Workers 上接入 Apple StoreKit 2 服务端验证,这篇避坑指南值 10 个小时的调试时间。

最近在为 VoiceRefine 搭建 Serverless 后端时,我以为接入 Apple 的 IAP(应用内购买)验证不过是“调包、配置、跑通”的半小时工作量。结果?我硬是把 Verify 接口从 HTTP 500 调到了 403,最后还得手写加密校验逻辑才算通关。

这里复盘一下我们踩过的三大“天坑”,以及最终的完美解决方案。


🛑 第一关:环境的“薛定谔”状态

现象: 代码写得很完美,配置了 Production 环境。一跑测试,报错:Error: appAppleId is required,或者直接验证失败。

真相: Apple 的支付环境并不像我们想象的那么非黑即白(生产 vs 沙盒)。实际上,你面对的是三种环境:

  1. Production (App Store 下载的)
  2. Sandbox (TestFlight / 沙盒账号测试的)
  3. Xcode Local (本地 .storekit 文件模拟的)

最坑的是,你的服务器往往不知道客户端发来的是哪种 Token。如果你硬编码用 Production 证书去验 Sandbox 的单子,Apple 直接甩脸。

解法:双环境自动降级 (Dual-Environment Fallback) 别去猜环境。先试 Production,失败了(且报错不是网络问题)就自动回退试 Sandbox。这是业界铁律。

try {
  // 1. 先试生产环境
  await verifyProd(token);
} catch (e) {
  // 2. 失败了?别急,试试沙盒
  console.warn("Prod failed, trying Sandbox...");
  await verifySandbox(token);
}

IAP 双环境自动降级流程


🚫 第二关:本地调试的“伪造证书”陷阱

现象: 为了方便调试,你在 Xcode 里开启了 StoreKit Testing。结果服务器端死活过不去验证,日志显示:environment: "Xcode"

真相: 这是一个巨大的认知盲区。Xcode 本地测试产生的 JWS Token 是自签名的! Apple 的全球根证书 (AppleRootCA-G3) 根本不认这些本地 Token。这就像你自己画了一张美元去验钞机检验,验钞机肯定报警。

很多开发者在这里卡很久:明明代码逻辑是对的,为什么一直在报“签名无效”?

解法:特殊通道 (The Xcode Bypass) 我们需要给本地调试开一个“后门”。检测到 environment === 'Xcode' 且 Bundle ID 是自家的,就直接“假装”验证通过。

注意:上线前记得确保这个逻辑只针对特定 Bundle ID 生效,别把后门开给黑客。

Xcode 本地调试豁免逻辑


☠️ 最终 Boss:Cloudflare 的 Crypto 兼容性噩梦

现象: 终于搞定了环境,上了 Cloudflare Workers,以为稳了。结果部署后直接 Exception 500NotSupportedError: Unrecognized or unimplemented EC curve "id-ecPublicKey"

真相: 这是本次最大的坑。 Apple 的官方库 @apple/app-store-server-library 是为标准的 Node.js 环境编写的,它深度依赖 Node 的 crypto 模块来处理椭圆曲线算法(ES256)。 然而,Cloudflare Workers 的 node:crypto 只是个 Polyfill(兼容层),它并不支持 Apple 库里用到的那种特定的曲线参数提取方式。

简单说:官方库在 Workers 上水土不服,且无解。

解法:抛弃官方库,拥抱 Web Crypto 既然官方库用不了,我们就自己造轮子! 我们用 jose 这个库重写了整个 JWS 验证逻辑。jose 基于标准的 Web Crypto API 构建,完美适配 Cloudflare Workers / Edge 运行时。

核心代码秀(Show Me The Code): 我们手动提取 Token 里的证书链 (x5c),用 importX509 导入公钥,再用 jwtVerify 验签。

// 纯净版 JWS 验证,不依赖 Node Crypto
import { importX509, jwtVerify, decodeProtectedHeader } from "jose";

async function verifyAppleJWS(token: string) {
  // 1. 拿 Header 里的证书链
  const header = decodeProtectedHeader(token);
  const leafCert = `-----BEGIN CERTIFICATE-----\n${header.x5c[0]}\n-----END CERTIFICATE-----`;
  
  // 2. 导入公钥 (ES256) - Cloudflare 支持这个!
  const publicKey = await importX509(leafCert, "ES256");

  // 3. 验证签名
  const { payload } = await jwtVerify(token, publicKey, { algorithms: ["ES256"] });
  
  return payload;
}

这段代码一下去,Verify success 的日志瞬间就出来了,那种舒爽感,这就叫降维打击

Cloudflare Crypto 兼容性对比


🎯 总结

在 Serverless 时代,不要盲目信任官方 SDK,特别是那些老牌大厂(Apple/AWS)的 SDK,它们往往带着沉重的 Node.js 历史包袱。

VoiceRefine 的 IAP 验证最终架构

  1. 自动识别 iOS/Watch 端 Bundle ID。
  2. 自动降级 Prod -> Sandbox。
  3. 自动豁免 Xcode 本地调试请求。
  4. 底层重构jose 替代官方库,适配 Edge 环境。

VoiceRefine IAP 验证最终架构

踩完这些坑,现在的支付验证服务,稳得像一块磐石。🚀


Edit page
Share this post on:

Previous Post
Dev 2.0:还没在 IDE 里用自然语言「装技能」?你正在错过 AI 时代的 `npm install`
Next Post
告别 PS 搬砖:我也能用 Python 「生成」 App Store 截图了?