第一次接 IAP,在真机上跑 App(模拟器跑不了真实沙盒购买,只能用本地 StoreKit 配置文件),点 Buy,结果是这三种之一:购买弹窗出现后立即报 “Your purchase could not be completed”;弹窗根本没出来,报 “Cannot connect to iTunes Store”;或者 Product.products(for:) 返回空数组,paywall 没东西可显示。代码没动过;昨天还好好的集成今天就崩了。
最快修复,按顺序:(1) iOS 18+ 上,在 Settings → Developer → Sandbox Apple Account 登入一个 Sandbox Apple Account(必须先开 Developer Mode)。(2) 确认 App Store Connect 的 Paid Apps 协议、税务、银行全部为 Active。(3) 确认每个 IAP 产品至少是 Ready to Submit。大约 80% 的沙盒失败就是这三项之一。本文剩下的部分会把完整的七步顺序走一遍,让你不用再瞎猜。
沙盒 IAP 的接线比 API 表面看的多得多:设备登入的 Sandbox Apple Account、它的地区和密码状态、App Store Connect 协议、IAP 产品状态、bundle ID、App ID 的 capability,以及任何 StoreKit 配置文件,每一项都要对齐。任何一项漂移,报错信息都含糊得指不到原因。
TestFlight、沙盒、模拟器:先搞清你在哪个环境
这三种行为不一样,认错环境能浪费你几个小时:
| Build 来源 | 环境 | 用的 Apple 账号 | 能清记录吗 |
|---|---|---|---|
| Xcode → 真机 | 沙盒 | 你登入的 Sandbox Apple Account | 能,在 App Store Connect |
| Xcode → 模拟器 | 只有本地 .storekit 文件 | 无(由文件驱动) | 不适用(在 Xcode 右键 reset) |
| TestFlight | 沙盒后端,但用真 Apple ID | 测试者的正常 Apple ID,不登沙盒账号 | 不能 |
最大的坑:TestFlight 购买免费、走沙盒后端,但用的是测试者的真 Apple ID,不是 Sandbox Apple Account。所以下面”登入沙盒账号”那些步骤适用于 Xcode 真机跑,不适用于 TestFlight。(来源:Apple, Testing subscriptions and IAP in TestFlight。)
常见原因
按命中率排序。
1. 设备上没登 Sandbox Apple Account
这是最常见的单个原因,而且 Apple 改了它的位置。测 IAP 不需要登出你的真 Apple 账号(那只在测 Apple Pay 时才需要)。你只要把一个 Sandbox Apple Account 登进专门的沙盒槽位:
- iOS 18 及以后:
Settings → Developer → Sandbox Apple Account。要先开 Developer Mode(Settings → Privacy & Security → Developer Mode,然后重启)。 - iOS 12 到 17:
Settings → App Store,滚到 Sandbox Account 那一节。这些版本上,该节只在你用开发签名的 build 尝试过一次购买之后才出现。
两个槽位都没有 Sandbox Apple Account 的话,iOS 就回退用你的真 ID,购买就失败,因为真 ID 买不了沙盒产品。
如何判断:按你的 iOS 版本去上面的路径看。沙盒槽位空着或显示你的真 ID,就是这个原因。(来源:Apple, Create a Sandbox Apple Account。)
2. App Store Connect 协议、银行、税务没填完
Paid Apps 协议没签、银行 / 税务表无效、状态不是 Active 之前,任何 IAP 产品都跑不通,沙盒也一样。Apple 会在 Product.products(for:) 的结果里静默地把这个产品丢掉。
如何判断:App Store Connect → Agreements, Tax, and Banking。看 Action Items 里有没有红,或状态不是 Active。缺 W-9 / W-8BEN、银行过期、协议修订没签都会挡沙盒。
3. IAP 产品是 “Missing Metadata” 或未提交状态
你建了产品起了名,但没加本地化、审核截图、定价。产品永远到不了 StoreKit 能返回的状态。Product.products(for:) 对这个 ID 返回空数组。
如何判断:App Store Connect → 你的 App → In-App Purchases。状态必须是 Ready to Submit 或 Approved。Missing Metadata 就查询不到。
4. StoreKit 配置文件和生产对不上
你为本地测试加了 .storekit 文件。Xcode 在用它,而不是查 App Store Connect,文件里的 ID 和生产不一致。你代码以为某产品存在,真沙盒里没有。
如何判断:Product → Scheme → Edit Scheme → Run → Options → StoreKit Configuration。选了文件,StoreKit 就从那里读。要么设成 None,要么把文件里的 product ID 和 App Store Connect 同步。
5. IAP 注册的 bundle ID 和 build 的不一致
IAP 产品注册在 com.acme.app 下,但 build 的 bundle ID 是 com.acme.app.dev 或 com.acme.app.staging。StoreKit 只返回当前运行 bundle ID 下注册的产品。
如何判断:打开 Info.plist,把 CFBundleIdentifier 和 App Store Connect 里 IAP 产品所在的 App ID 对一遍。任何不一致(包括大小写)都查不到。
6. Sandbox Apple Account 地区错或卡死
沙盒账号锁 storefront。你建的账号在美区 storefront,但设备地区解析到别处,价格和产品可用性就会出问题。账号也会在中断购买或长跑订阅测试后卡死。注意:截至 2026 年,你可以在 App Store Connect 改沙盒账号的地区,但创建后改不了它的邮箱、姓名、密码。
如何判断:App Store Connect → Users and Access → Sandbox。看 storefront / 地区。先在 Settings → Developer → Sandbox Apple Account(iOS 18+)试登入;登不进说明问题在账号本身,不在你 App。卡死了就清它的购买记录(见”如果还是没修好”)或建一个新的。
7. App ID 没开 In-App Purchase capability
Apple Developer → Certificates, Identifiers & Profiles → Identifiers → 你的 App ID → Capabilities 里,In-App Purchase 必须开启。没开的话,即使产品在 App Store Connect 里,build 也跑不通。IAP 需要显式 App ID(不能用通配符 *)。
如何判断:Apple Developer 门户 → 你的 App ID → Capabilities 列表。In-App Purchase 没勾就是它。
需要收集的信息
- 购买弹窗的具体错误文案,以及 Console.app 按你 bundle ID 过滤的日志(找
AMSStatusCode和SKErrorDomain那些条目)。 - 沙盒账号的邮箱和它的 storefront / 地区。
- App Store Connect 协议 / 税务 / 银行的状态。
- 各 IAP 产品的状态(至少
Ready to Submit)。 - 你 build 的 bundle ID 和 Developer Portal 里的 App ID。
- 运行 scheme 里是否选了某个
.storekit配置文件。
最短修复路径
Step 1:在设备上配 Sandbox Apple Account
iOS 18+:Settings → Developer → Sandbox Apple Account → Sign In。(设置里没有 Developer 的话,先在 Settings → Privacy & Security → Developer Mode 开 Developer Mode 并重启。)iOS 12–17 上槽位在 Settings → App Store → Sandbox Account,且只在开发签名 build 尝试过一次购买后才出现。
先在 App Store Connect → Users and Access → Sandbox 建账号。用一个还不是 Apple 账号的唯一邮箱(像 you+sandbox@icloud.com 这样的 +sandbox 子地址就行,邮箱子地址是官方支持的)。测 IAP 不需要登出你的真 Apple 账号。
如果提示 “This Apple ID has not yet been used in the iTunes Store”,在设备上点提示接受一次沙盒条款。
Step 2:核对 App Store Connect 协议
App Store Connect → Business → Agreements, Tax, and Banking:
- Paid Apps 协议状态
Active。 - 银行信息:完整未过期。
- 税务信息:完整(美区开发者:W-9;非美:W-8BEN,符合条件加 treaty)。
- “Action Items” 面板里没遗留事项。
任一红条沙盒交易就会静默失败。先修这些再调代码。
Step 3:核对每个 IAP 产品
App Store Connect → 你的 App → In-App Purchases。每个产品都要:
- Display Name、Description(按 locale)。
- 定价 tier 设了。
- 审核截图(任何图都行,不必是真实 UI)。
- 状态:
Ready to Submit或Approved。
Missing Metadata 的产品对 Product.products(for:) 不可见。
Step 4:核对代码里的 product ID
// 硬编码集合或从你服务器拉
let productIDs: Set<String> = [
"com.acme.pro_monthly",
"com.acme.coins_100"
]
let products = try await Product.products(for: productIDs)
print("Returned \(products.count) products of \(productIDs.count) requested")
Returned 少于 requested 就列出缺的 ID,和 App Store Connect 逐字符核拼写——com.acme.pro_monthly 和 com.acme.proMonthly 是静默漏掉的差别。
Step 4b:确认没有多余的 .storekit 文件劫持查询
如果 App Store Connect 里一切都对,products 还是返回空,那 Xcode 可能在读本地 StoreKit 配置文件,而不是真沙盒。检查 Product → Scheme → Edit Scheme → Run → Options → StoreKit Configuration。真沙盒测试时这里必须设成 None。选了文件,StoreKit 就只返回那个文件里的产品,永远不查 App Store Connect。
Step 5:在 build 里直接测
Xcode 在真机上跑 App,触发购买流程。iOS 提示 Apple ID 时用你的 Sandbox Apple Account(或接受 Step 1 自动填的)。
看到 “Cannot connect to iTunes Store” 时检查:
- 设备网络可达。
- 日期时间正确(TLS 证书校验依赖它)。
- VPN 关(某些 VPN 路由经过沙盒被屏蔽的地区)。
Step 6:购买成功后在 App Store Connect 核验
App Store Connect → Sales and Trends → Sandbox(或 Transactions 视图)。成功的沙盒购买几分钟内出现。没出现就说明 Apple 那边交易从没完成,你 client 记了一个假成功。用 StoreKit 2 的话,再读 Transaction.currentEntitlements 确认结果,别只信购买回调。
怎么确认已经修好
- 购买弹窗显示正确产品名和价格(沙盒按沙盒账号 storefront 的货币显示价格)。
- 点 Buy 认证后弹窗带绿勾消失。
Transaction.updates推出一条 verified transaction,含预期 product ID,并且该 ID 出现在Transaction.currentEntitlements里。- App Store Connect → Users and Access → Sandbox 几分钟内列出该笔购买。
- 同沙盒账号再跑一次走 re-purchase 流(订阅自动续;非消耗品立即 restore)。
如果还是没修好
- 清沙盒账号购买记录:App Store Connect → Users and Access → Sandbox → 点账号 → Clear Purchase History。这能重置卡死的订阅或中断购买状态。
- 在 iOS 设置里把 Sandbox Apple Account 登出再登入(iOS 18+ 在
Settings → Developer → Sandbox Apple Account)。 - 把 scheme 的 StoreKit Configuration 设成
None,强制走真沙盒查询(见 Step 4b)。 - 建一个新沙盒账号,storefront 匹配设备地区;老账号会卡死。
- 用第二个沙盒账号测,排除单账号问题。
- 如果是服务器校验收据,把校验请求指向沙盒校验端点(旧版 verifyReceipt 流用
https://sandbox.itunes.apple.com/verifyReceipt;StoreKit 2 / 签名交易校验用 App Store Server API 的沙盒 base URL)。一个常见的假失败是把沙盒收据发给了生产校验端点(状态21007)。
预防建议
- 写任何 IAP 代码前先把协议、税务、银行配齐——后面一切都依赖它们。
- 你打算支持的每个 storefront 都建一个沙盒账号,凭证存团队密码管理器。
- 仓库里维护
STOREKIT.md列每个 product ID 及它定义的位置(App Store Connect 加任何.storekit文件),每次发版 diff。 - CI 加一步对沙盒跑
Product.products(for:),任何预期 ID 缺失就 fail。 - 每次发版前在真机测一次购买;沙盒状态会漂移,这样静默失败能早发现。
FAQ
测 IAP 必须登出我的真 Apple ID 吗?
不用。测 in-app purchase 时,你把一个 Sandbox Apple Account 登进专门的沙盒槽位(iOS 18+ 在 Settings → Developer → Sandbox Apple Account),真账号不动。只有测 Apple Pay 沙盒才需要登出设备 Apple 账号。
为什么 Product.products(for:) 返回空数组?
通常三个原因,按可能性排序:产品还不是 Ready to Submit(卡在 Missing Metadata);Paid Apps 协议 / 税务 / 银行不是 Active;或者 product ID 打错(pro_monthly 和 proMonthly)。scheme 里选了 .storekit 配置文件也会盖掉 App Store Connect 的产品。
TestFlight 用沙盒账号吗? 不用。TestFlight 走沙盒后端,但用测试者的真 Apple ID,且不扣费。TestFlight 不要登 Sandbox Apple Account,而且 TestFlight 的购买记录不能像沙盒账号那样清掉。
代码报成功,但 App Store Connect 里没有这笔交易——怎么回事?
client 信了一个并不代表服务器侧购买完成的回调。用 StoreKit 2 的话,对着 Transaction.currentEntitlements 校验,再看 App Store Connect 的 Sandbox 交易。如果服务器校验收据,状态 21007 表示把沙盒收据发给了生产校验端点。
沙盒购买能在 iOS 模拟器里测吗?
碰真沙盒不行。模拟器只能用本地 .storekit 配置文件。任何碰 Apple 沙盒服务器的环节(Sandbox Apple Account 流程、真实交易、App Store Connect 记录)都需要开了 Developer Mode 的真机。
沙盒订阅续期有多快? 比生产快得多,长周期会压缩到几分钟。默认情况下,1 个月的订阅在沙盒里大约每 5 分钟续一次,续 12 次后自动续期停止。你可以在 App Store Connect → Users and Access → Sandbox 按账号调续期速率。