第一次接 IAP,在真机上跑 App(模拟器跑不了真实购买),点 Buy,弹窗出现后立即报 “Your purchase could not be completed”,或者弹窗根本没出来报 “Cannot connect to iTunes Store”,或者弹窗问 Apple ID 密码、拒绝你沙盒账号的凭证。代码没动过;昨天还好的集成今天就崩了。
沙盒 IAP 的接线比 API 表面看的多得多:设备登录的 Apple ID、沙盒账号的地区和密码状态、App Store Connect 协议、IAP 产品状态、StoreKit 配置文件,每一项都要对齐。任何一项漂移,报错信息往往含糊得指不到原因。
常见原因
按命中率排序。
1. 真 Apple ID 还在 App Store 里登录着
iOS 12 之前要去 Settings → iTunes & App Store 登出真 ID。iOS 12+ 加了 Settings → App Store → Sandbox Account 单独入口,但两者都没设的话 iOS 用你的真 ID 去购买,真 ID 买不了沙盒产品就失败。
如何判断:Settings → App Store。滚到 Sandbox Account(iOS 12+)。空着或显示你的真 ID 就是这个。
2. App Store Connect 协议、银行、税务没填完
Paid Apps 协议没签、银行 / 税务表无效、状态不是 Active 之前,任何 IAP 产品包括沙盒都跑不通。Apple 在 requestProducts 时静默返回 “product not available”。
如何判断:App Store Connect → Agreements, Tax, and Banking。看 Action Items 里有没有红,或状态不是 Active。缺 W-9 / W-8BEN、银行过期、协议修订没签都会挡沙盒。
3. IAP 产品是 “Missing Metadata” 或未提交状态
你建了产品起了名,没加本地化、审核截图、定价。产品永远到不了 StoreKit 能返回的状态。Products.products(for:) 返回空数组。
如何判断:App Store Connect → 你的 App → In-App Purchases。状态必须是 Ready to Submit 或 Approved。Missing Metadata 就查询不到。
4. StoreKit 配置文件和生产对不上
你为本地测试加了 .storekit 文件。Xcode 在用它,而不是查 App Store Connect,文件里的 ID 和生产不一致。你代码以为某产品存在,真沙盒没有。
如何判断:Xcode → scheme 设置 → Run → Options → StoreKit Configuration。选了文件就 StoreKit 从那里读。要么清掉选择,要么把文件 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. 沙盒账号地区错或密码过期
沙盒账号有地区锁。建的账号在美区但设备地区设成日本,账号买不了。密码也会过期(沙盒密码比生产过期更快)。
如何判断:App Store Connect → Users and Access → Sandbox → Testers。看地区和最近一次密码重置日期。先在 Settings → App Store → Sandbox Account 试登录;密码挂就去 App Store Connect 重置。
7. App ID 没开 In-App Purchase capability
Apple Developer → Identifiers → 你的 App ID → Capabilities 里 In-App Purchase 必须勾上。没勾,即使产品在 App Store Connect 也跑不通。
如何判断:Apple Developer 门户 → 你的 App ID → 看 Capabilities 列表。In-App Purchase 没勾就是它。
动手前先确认
- 弄清你在测的是 TestFlight(默认走沙盒)、本地 build(走沙盒)、还是生产(真钱)——行为不同。
- 确认设备是真机不是模拟器——真实沙盒购买只在真机上跑。
- 改之前把当前 App Store Connect IAP 产品配置截图备份。
- 在沙盒账号上确保能稳定复现问题再开始改。
需要收集的信息
- 购买弹窗的具体错误文案 + Console.app 按你 bundle ID 过滤的日志。
- 沙盒账号的邮箱、地区、密码重置日期。
- App Store Connect 协议 / 税务 / 银行的状态。
- 各 IAP 产品的状态(至少
Ready to Submit)。 - 你 build 的 bundle ID 和 Developer Portal 里的 App ID。
- 是否启用了
.storekit配置文件。
最短修复路径
Step 1:在设备上配沙盒账号
Settings → App Store → Sandbox Account → Sign In。用你在 App Store Connect → Users and Access → Sandbox → Testers 建的沙盒账号。用一个不绑任何真 Apple ID 的唯一邮箱(用 +sandbox alias 也行)。
如果提示 “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 的产品对 Products.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 5:在 build 里直接测
Xcode 在真机上跑 App,触发购买流程。iOS 提示 Apple ID 时用你的沙盒账号(或接受 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 是假成功。
怎么确认已经修好
- 购买弹窗显示正确产品名和价格(沙盒在你沙盒账号地区货币下显示 tier 价格)。
- 点 Buy 认证后弹窗带绿勾消失。
Transaction.updates推出一条 verified transaction,含预期 product ID。- App Store Connect → Sandbox Transactions 几分钟内列出该笔购买。
- 同沙盒账号再跑一次走 re-purchase 流(订阅自动续;非消耗品立即 restore)。
如果还是没修好
- 在 App Store Connect → Users and Access → Sandbox → Testers → 点账号 → Clear History 清沙盒账号购买记录。
- iOS 设置里把 Sandbox Account 登出再登入。
- 删掉 scheme 里的
.storekit配置强制走真沙盒查询。 - 建一个新沙盒账号,地区匹配设备地区;老账号会进入卡死状态。
- 用第二个沙盒账号测,排除单账号问题。
预防建议
- 写任何 IAP 代码前先把协议、税务、银行配齐——后面一切都依赖它们。
- 给你打算支持的每个地区都建沙盒账号,密码存团队密码管理器并文档化。
- 仓库里维护
STOREKIT.md列每个 product ID 及它定义的位置(App Store Connect +.storekit文件),每次发版 diff。 - CI 加一步对沙盒跑
Products.products(for:),任何预期 ID 缺失就 fail。 - 每周在真机测一次购买;沙盒状态会漂移,静默失败要早发现。