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 沙盒)。实际上,你面对的是三种环境:
- Production (App Store 下载的)
- Sandbox (TestFlight / 沙盒账号测试的)
- 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);
}

🚫 第二关:本地调试的“伪造证书”陷阱
现象:
为了方便调试,你在 Xcode 里开启了 StoreKit Testing。结果服务器端死活过不去验证,日志显示:environment: "Xcode"。
真相:
这是一个巨大的认知盲区。Xcode 本地测试产生的 JWS Token 是自签名的!
Apple 的全球根证书 (AppleRootCA-G3) 根本不认这些本地 Token。这就像你自己画了一张美元去验钞机检验,验钞机肯定报警。
很多开发者在这里卡很久:明明代码逻辑是对的,为什么一直在报“签名无效”?
✅ 解法:特殊通道 (The Xcode Bypass)
我们需要给本地调试开一个“后门”。检测到 environment === 'Xcode' 且 Bundle ID 是自家的,就直接“假装”验证通过。
注意:上线前记得确保这个逻辑只针对特定 Bundle ID 生效,别把后门开给黑客。

☠️ 最终 Boss:Cloudflare 的 Crypto 兼容性噩梦
现象:
终于搞定了环境,上了 Cloudflare Workers,以为稳了。结果部署后直接 Exception 500:
NotSupportedError: 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 的日志瞬间就出来了,那种舒爽感,这就叫降维打击。

🎯 总结
在 Serverless 时代,不要盲目信任官方 SDK,特别是那些老牌大厂(Apple/AWS)的 SDK,它们往往带着沉重的 Node.js 历史包袱。
VoiceRefine 的 IAP 验证最终架构:
- 自动识别 iOS/Watch 端 Bundle ID。
- 自动降级 Prod -> Sandbox。
- 自动豁免 Xcode 本地调试请求。
- 底层重构 用
jose替代官方库,适配 Edge 环境。

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