拒回引 Guideline 3.1.1,文案像 “the app does not allow users to restore previously purchased non-consumable in-app purchases or subscriptions”。你的付费墙有醒目的 Subscribe 和 Buy Now 按钮,但没有 Restore。或者有,但藏在 Settings → Account → Subscriptions → 点两下,审核员从启动后撞到的付费墙里根本找不到。或者你用邮箱密码登录、把它当成”restore 路径”,但 Apple 期望的是一个单独调 StoreKit 的按钮。
任何非消耗品 IAP 或自动续订订阅,Apple 都期望有可见、可用的 Restore Purchases 入口。修复是机械的:付费墙加一个带文案的按钮、设置加一个顶层入口、绑 StoreKit restore 调用、把成功 / 失败状态显式露出,下次提交就过。
常见原因
按命中率排序。
1. 付费墙只有 Buy 没有 Restore
原始设计为转化优化,每像素都喊 “buy”。Restore 被当”以后做的”从未加上。
如何判断:干净 build 打开付费墙。不滚动数不到一个 “Restore Purchases” 链接或按钮就是这种情况。审核员侧:拒回引用的就是付费墙截图。
2. Restore 在但藏在深层导航
Restore 在 Settings → Account → Subscription → Manage → Restore。60 秒的审核员找不到。Apple 期望 Restore 从付费墙或主设置一两步可达。
如何判断:从启动到 Restore 按钮数点击次数。超过 2 下就太深。
3. App 把账号登录当 restore
你有邮箱登录。用户登账号、服务器告诉他们是 Pro。你以为这就是 “restore”。Apple 不同意——restore 必须调 StoreKit 并和 Apple 的购买历史对账,不能只靠你的账号库。
如何判断:搜代码里有没有 restoreCompletedTransactions 或 Transaction.currentEntitlements。没从 UI 按钮调过任一就是缺 StoreKit restore 原语。
4. iOS 上有 restore 但新设计的屏没有
你最近重做了付费墙。旧的有 Restore;新的因为工程师 porting 设计时漏了小文字链接,上线后没有。iOS 生产里缺,Android 还能用。
如何判断:把当前付费墙 view 和上一版 git 历史 diff。如果 Restore Purchases view 或按钮被删了,就是这次回归。
5. Restore 跑了但没显式反馈
按钮在。点了调 StoreKit。但视觉上什么都没发生——没 loading spinner、没成功提示、没失败信息。审核员点了什么都没看到,认为坏了,拒。
如何判断:干净安装手动点一下 Restore。5 秒内看不到明确反馈,审核员也看不到。
6. Restore 藏在免费层登录墙后
付费墙可达,但 Restore 按钮只有用户注册免费账号后才出现。Apple 期望 restore 不用任何注册就能触达;否则老付费用户得新注册一个免费账号才能 restore。
如何判断:冷装、不注册、走到付费墙。Restore 不可见就要修。
动手前先确认
- 读一遍 Apple App Review Guidelines 3.1.1,让实现和期望逐字对齐。
- 决定 Restore 放哪:付费墙 + 设置是安全默认;Onboarding 可选。
- 改之前把当前付费墙和设置截图归档。
- 弄清你用的是 StoreKit 1 还是 2,restore API 不同。
需要收集的信息
- 审核员拒回原文和引用的 Guideline 子条款。
- 当前付费墙、设置、任何 restore 流程的截图。
- 你的 StoreKit 版本(SK1 vs SK2)和当前 restore 实现。
- App 是否有免费层用户,他们不该启动就看到付费墙。
- 老用户当前点 Restore 的频率(帮你 UX 调位)。
最短修复路径
Step 1:付费墙加 Restore Purchases 按钮
在付费墙 view 里放一个清晰的 “Restore Purchases” 文字链接或按钮。标准放在 Buy / Subscribe 主 CTA 下方,字号更小(但可读,≥14pt)。不要刻意视觉弱化。
// SwiftUI 示例
VStack(spacing: 16) {
Button("Subscribe — $9.99/month") { /* buy */ }
.buttonStyle(.borderedProminent)
Button("Restore Purchases") {
Task { await store.restore() }
}
.font(.subheadline)
.foregroundColor(.secondary)
}
Step 2:设置里加第二个入口
从来没撞到付费墙的用户(比如新装的老订阅用户)也要能 restore。Settings → Restore Purchases 加成顶层一行,不要埋在 Account 下。
NavigationLink(destination: SettingsView()) {
Section {
Button("Restore Purchases") {
Task { await store.restore() }
}
}
}
Step 3:把 restore 动作接到 StoreKit
StoreKit 2:
func restore() async {
isRestoring = true
do {
try await AppStore.sync() // 露出任何漏掉的 transaction
for await result in Transaction.currentEntitlements {
guard case .verified(let txn) = result else { continue }
await applyEntitlement(productID: txn.productID)
}
statusMessage = "Restored successfully."
} catch {
statusMessage = "Restore failed: \(error.localizedDescription)"
}
isRestoring = false
}
StoreKit 1:
SKPaymentQueue.default().restoreCompletedTransactions()
// 实现 paymentQueueRestoreCompletedTransactionsFinished + paymentQueue:restoreCompletedTransactionsFailedWithError
Step 4:进度和结果显式露出
点击后:
- 立即出 loading spinner 或进度文字。
- 成功:显示 “Your purchases were restored” 列出找到的产品名。2 秒后自动收起。
- 无购买:显示 “No previous purchases found”。
- 错误:显示错误信息加重试按钮。
审核员点击后 5 秒内必须看到东西在发生。
Step 5:在 App Review notes 里写明路径
加到 App Review Information:
RESTORE PURCHASES
- Available on the paywall (bottom, "Restore Purchases" link).
- Also available in Settings > Restore Purchases.
- Tap the button; on a fresh install, this triggers StoreKit sync.
- Test with sandbox tester apple-iap-sandbox@yourdomain.com (Pro is pre-purchased).
Step 6:重新提交
App Store Connect → App Store → 点 build → Submit for Review。Restore 按钮这类不动其他 UI 的修复过审很快,预期 24 小时中位。
怎么确认已经修好
- 冷装 build 在付费墙不滚动就能看到 Restore Purchases。
- Settings 有顶层 Restore Purchases 行。
- 点 Restore 立即显示进度反馈。
- 有购买记录的沙盒账号点 Restore 后立即恢复权限。
- 无购买记录的新账号点 Restore 看到 “No previous purchases found”。
如果还是没修好
- 拒回还在就在 Resolution Center 附 30 秒 QuickTime 屏录,演示从付费墙到结果的完整 Restore 流。
- 确认 restore 调用真的接到 StoreKit(Console.app 看
StoreKit.Transaction日志);没接到就是按钮在代码也坏。 - 用多个沙盒账号测一遍,确认不同账号状态都能跑通。
- 检查冷启动时 Restore 按钮是否启用(不是灰的);有些实现把 Restore gating 在网络调用后。
预防建议
- 把 Restore Purchases 当付费墙和设置必备 UI 元素——纳入组件库。
- 加 UI 测试:每个 PR 都断言付费墙和设置里 Restore 按钮可见。
- 把 Restore 接线写进项目脚手架 / 模板,新项目永远不漏。
- 每次付费墙重设计都审 diff,专门核查 Restore 有没有被重构掉。
- 跟踪 Restore 点击频率;发版后突然下降意味着回归。