Restore Purchases 按钮缺失:修复 3.1.1 拒回

Apple 以 Guideline 3.1.1 打回、提示找不到 Restore Purchases 按钮。在付费墙和设置加上按钮、接到 StoreKit、重新提交即可过审。

最快修复: 在付费墙上放一个可见的 Restore Purchases 按钮(放在主购买 CTA 下方),同时在 Settings 里加一个顶层入口,接到 StoreKit restore 调用(StoreKit 2 用 AppStore.sync()),点击后弹出结果提示,然后重新提交,并在审核备注里一句话写清按钮在哪。对大多数 App 这只是改 UI,过审很快。

拒回引 Guideline 3.1.1,Apple 的措辞通常是一句 “We were unable to locate a way to restore previously purchased non-consumable in-app purchases or subscriptions in your app.”。三种情况会触发:(1) 付费墙有醒目的 SubscribeBuy Now 按钮,但没有 Restore;(2) 有 Restore,但藏在 Settings → Account → Subscriptions,审核员从启动撞到的付费墙里找不到;(3) 你用邮箱密码登录、把它当成”restore 路径”,但 Apple 仍然要一个直接调 StoreKit 的按钮。

任何非消耗品 IAP 或自动续订订阅,Apple 的 App Review Guideline 3.1.1 都期望有可见、可用的 Restore Purchases 入口(消耗品和非续订订阅不需要)。截至 2026 年 6 月,这条要求和下面的修复步骤和往年一致;变的只是 App Store Connect 的提交流程改了名(见 Step 6)。

你属于哪一类?

对号入座,直接跳到对应修复。

现象大概原因修复
付费墙只有购买 / 订阅 CTA从没加 RestoreStep 1
有 Restore 但审核员”找不到”藏在深层导航Step 1 + Step 2
你让 Apple 用邮箱登录没接 StoreKit 调用Step 3
以前能 restore,重设计后没了UI 回归diff 付费墙,再走 Step 1
按钮在,审核员”点了没反应”没进度 / 结果 UIStep 4
Restore 注册后才出现restore 前有登录墙挪出来,Step 1

常见原因

按命中率排序。

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 的购买历史对账,不能只靠你的账号库。

如何判断:搜代码里有没有 restoreCompletedTransactionsTransaction.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 不可见就要修。

需要收集的信息

  • 审核员拒回原文和引用的 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 2AppStore.sync()——这是 Apple 文档里给出的、对应旧 restoreCompletedTransactions() / SKRefreshReceiptRequest 的方法。这里有两点要注意:

  • AppStore.sync() 会弹出系统登录提示,要求用户用 Apple Account 认证。所以只在用户明确点按钮时调,绝不要在启动时自动调。
  • 正常运行根本不需要 sync()——Transaction.currentEntitlements 已经反映用户当前的有效购买,StoreKit 会在后台保持它更新。sync() 是 restore 按钮的手动强制刷新。
func restore() async {
    isRestoring = true
    do {
        try await AppStore.sync()  // 强制刷新;会触发 Apple Account 登录提示
        var restoredAny = false
        for await result in Transaction.currentEntitlements {
            guard case .verified(let txn) = result else { continue }
            await applyEntitlement(productID: txn.productID)
            restoredAny = true
        }
        statusMessage = restoredAny ? "Restored successfully." : "No previous purchases found."
    } 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、把新 build 挂到版本上,然后点 Add for Review 再点 Submit for Review(App Store Connect 把原来单步的 “Submit for Review” 拆成了这个两步提交流程)。如果拒回还在 Resolution Center 开着,你也可以直接在那里回复、把同一个 build 推过去。Restore 按钮这类不动其他 UI 的修复过审很快;截至 2026 年 6 月,Apple 公开称 90% 的提交在 24 小时内完成审核(高峰期实际耗时可能更长)。

怎么确认已经修好

  • 冷装 build 在付费墙不滚动就能看到 Restore Purchases。
  • Settings 有顶层 Restore Purchases 行。
  • 点 Restore 立即显示进度反馈。
  • 有购买记录的沙盒账号点 Restore 后立即恢复权限。
  • 无购买记录的新账号点 Restore 看到 “No previous purchases found”

如果还是没修好

  1. 拒回还在就在 Resolution Center 附 30 秒 QuickTime 屏录,演示从付费墙到结果的完整 Restore 流。
  2. 确认 restore 调用真的接到 StoreKit(Console.app 看 StoreKit.Transaction 日志);没接到就是按钮在代码也坏。
  3. 用多个沙盒账号测一遍,确认不同账号状态都能跑通。
  4. 检查冷启动时 Restore 按钮是否启用(不是灰的);有些实现把 Restore gating 在网络调用后。

预防建议

  • 把 Restore Purchases 当付费墙和设置必备 UI 元素——纳入组件库。
  • 加 UI 测试:每个 PR 都断言付费墙和设置里 Restore 按钮可见。
  • 把 Restore 接线写进项目脚手架 / 模板,新项目永远不漏。
  • 每次付费墙重设计都审 diff,专门核查 Restore 有没有被重构掉。
  • 跟踪 Restore 点击频率;发版后突然下降意味着回归。

常见问题

只要有内购就必须放 Restore Purchases 按钮吗? 不是。Guideline 3.1.1 只对可恢复产品要求 restore 机制:非消耗品 IAP 和自动续订订阅。消耗品(金币、一次性额度)和非续订订阅不可恢复,不需要这个按钮。

我的”登录”按钮不够吗——登录不就能恢复订阅吗? 对 App Review 不够。Apple 要的是一个调 StoreKit、并和 Apple Account 购买历史对账的按钮,独立于你自己的账号系统。老订阅用户必须能在不创建、不登录账号的情况下重新拿回权限。登录可以保留,但 StoreKit restore 按钮也得加。

Restore 按钮到底该放哪? 最低要求:放在付费墙上(不滚动就能看到),并在 Settings 里加一个顶层入口。Apple 自己的说法是放在”用户和审核员都能找到的地方”。一个可达的位置也能过,但两处都放是安全默认,能避免”导航藏太深”这类拒回。

为什么点 Restore 会弹出 Apple Account 登录提示? 那是 AppStore.sync() 的正常行为——它强制从 App Store 刷新并让用户认证。所以只在用户明确点击时调,绝不要自动调,否则会用突然的登录提示骚扰用户。如果你只是想检查当前权限,改读 Transaction.currentEntitlements,它不会弹提示。

审核员说按钮点了没反应,要给他们看什么? 点击后 5 秒内露出可见反馈:先出 spinner,再弹成功提示并列出恢复的产品、或”No previous purchases found”、或带重试的错误。即使调用悄悄成功了,审核员也会拒掉看起来坏的东西。在 Resolution Center 附一段演示完整流程的屏录。

重审多快? 只改 restore UI 的修复是最快过的重审之一。截至 2026 年 6 月,Apple 称 90% 的提交在 24 小时内完成审核,审核备注写清楚还能更快。

相关阅读

标签: #排查 #App Store #App 审核 #IAP / 内购