在上架前联调 IAP 时,我们遇到一个典型但非常迷惑的问题:
- 用户点击“继续购买”
- 按钮进入“处理中”
- 几秒后恢复“继续购买”
- 没有弹出 App Store 购买确认框
这篇文章记录完整排查路径、关键日志、根因以及可落地修复方案,适合发布前做 StoreKit 风险检查。
1. 问题现象
购买页商品列表能正常加载,点击购买按钮后:
purchase()被调用- 无系统弹窗
- UI 最终回到未购买态
乍看像是“购买流程没发起”,但日志显示并不这么简单。
2. 首轮排查:确认购买链路有没有走通
我们先把购买链路关键节点打全日志:
- 按钮点击参数
purchaseSelectedProduct()入口和 guard 分支product.purchase()前后Transaction.updatesrefreshEntitlements()Transaction.currentEntitlementsTransaction.latest(for:)
结果显示:
purchase()返回了.success- 交易校验成功、
transaction.finish()也执行了 - 但
entitledIDs仍为空
这说明:不是“没发起购买”,而是“购买后权益没被识别”。
3. 深挖后发现的关键事实
继续补日志后,出现了几个决定性信号:
Transaction.currentEntitlements迭代数为0Transaction.latest(for: monthly)有有效记录,但已过期- 更关键的是:购买前
Transaction.unfinished中存在旧交易 - 本次
purchase()返回的也是旧交易链路中的交易,而非新的交互购买流程
这就解释了为什么“不弹窗”:
- StoreKit 先返回了可处理的旧交易(unfinished / 历史链路)
- 你代码把它 finish 掉了
- 购买请求看起来“成功”,但没有走到用户期望的新购买确认弹窗路径
4. 根因总结
本次问题不是单点,而是两类因素叠加:
- 未完成交易(unfinished transaction)干扰购买流程
- 沙盒订阅链路处于过期状态(即使有 latest transaction,也可能不是 active entitlement)
最终表现就是:不弹窗、成功返回旧交易、权益仍为空。
5. 修复策略(实践有效)
5.1 购买前主动清理 unfinished 交易
在启动和点击购买前,扫描 Transaction.unfinished,对 verified transaction 执行 finish(),避免旧交易截胡新购买流程。
5.2 保留并强化诊断日志(上线前建议开启)
关键日志建议保留到上线前:
unfinished transaction快照subscription status(state、renewal info)currentEntitlements迭代数量latest(for:)的 transaction/expiration
5.3 权益判断采用“双通道”(可选增强)
若某些环境下 currentEntitlements 短暂为空,可用 latest(for:) 做回退判定(注意仍需校验是否 active)。
6. 上线前 IAP 检查清单
- 每次测试前确认 Sandbox 账号订阅状态(是否已过期)
- 关键路径打印
unfinished数量 - 测试“首次购买 / 过期后续订 / 恢复购买”三类场景
- 检查
currentEntitlements与latest是否一致 - 若出现“不弹窗”,先看是否被旧交易链路命中
7. 结语
“点击购买不弹窗”不一定是 UI 或调用时机问题。
在 StoreKit 2 下,交易状态、unfinished 队列、订阅生命周期都会影响最终表现。
上架前把这些日志和清理动作补齐,能减少大量线上不可复现的 IAP 问题。