在提审App Store Connect中,如果应用没有“恢复购买”功能,则可能会涉及Apple的App审核指南3.1.1条款的限制:
Guideline 3.1.1 - Business - Payments - In-App Purchase
The app offers in-app purchases that can be restored but does not include a "Restore Purchases" feature to allow users to restore the previously purchased in-app purchases.
翻译为:
指南 3.1.1 - 业务 - 付款 - 应用内购买
该应用提供可恢复的应用内购买,但不包含“恢复购买”功能,以允许用户恢复之前购买的应用内购买。
因此,应用需要提供“恢复购买”功能,以恢复内购商品。
处理“恢复内购”按钮
内购UI
创建一个按钮来触发恢复购买流程:
// 恢复内购按钮
Button(action: {
// 调用恢复内购代码
Task {
await iapManager.restorePurchases()
// ✅ 主线程更新 loadPurchased 和 endOfWait
DispatchQueue.main.async {
iapManager.loadPurchased = false // 结束加载动画
}
try? await Task.sleep(nanoseconds: 20_000_000_000) // 延迟 20 秒
DispatchQueue.main.async {
endOfWait = true // ✅ 主线程更新 endOfWait
}
}
DispatchQueue.main.async {
iapManager.loadPurchased = true // ✅ 显示加载动画
}
}, label: {
Text("Restore purchases")
.font(.footnote)
.foregroundColor(.gray)
})
需要注意的是,iapManager.loadPurchased是我的加载动画,当加载超过20秒会显示结束按钮,因此,实际的代码为:
// 恢复内购按钮
Button(action: {
// 调用恢复内购代码
Task {
await iapManager.restorePurchases()
}
}, label: {
Text("Restore purchases")
.font(.footnote)
.foregroundColor(.gray)
})
通过点击“恢复内购”按钮,使用Task调用restorePurchases方法,iapManager是一个管理内购的类,也可以将restorePurchases放在当前视图。
// 恢复内购按钮
Button(action: {
// 调用恢复内购代码
Task {
await restorePurchases()
}
}, label: {
Text("Restore purchases")
.font(.footnote)
.foregroundColor(.gray)
})
内购代码
// 调用恢复购买的函数
func restorePurchases() async {
let entitlements = Transaction.currentEntitlements
var hasTransactions = false
print("开始等待 Transaction.currentEntitlements")
for await result in entitlements {
hasTransactions = true
print("发现交易:\(result)")
switch result {
case .verified(let transaction):
print("处理已验证的交易")
// 处理已验证的交易
await handleVerifiedTransaction(transaction)
case .unverified(_, let error):
print("交易未验证,错误:\(error.localizedDescription)")
}
}
if !hasTransactions {
print("没有找到任何有效的交易,可能是用户没有购买过任何项目")
}
}
// 处理已验证的交易
func handleVerifiedTransaction(_ transaction: Transaction) async {
print("transaction:\(transaction)")
switch transaction.productType {
case .nonConsumable, .autoRenewable, .nonRenewable:
print("已恢复商品:\(transaction.productID)")
savePurchasedState(for: transaction.productID) // 恢复内购的方法
default:
print("其他类型商品无需恢复")
}
}
代码解析
1、let entitlements = Transaction.currentEntitlements
当调用restorePurchases方法后,会调用Transaction.currentEntitlements异步序列,返回当前有效的交易(entitlements)。
这个序列会返回所有未过期的订阅、非消耗型商品、和非续期订阅的交易信息。
但如果没有交易信息,则不会触发任何操作。
2、for await result in entitlements
这里的 for await 是 Swift 的 异步遍历,它会逐个获取 Transaction 结果。
如果有有效交易,它会进入 switch result 处理。
3、switch result逻辑解析
1)case .verified(let transaction)
验证成功的交易
交易通过 StoreKit 验证(没有被篡改),可以安全地进行恢复。
进入 handleVerifiedTransaction(transaction) 处理已恢复的商品。
2)case .unverified(_, let error)
未验证的交易
交易未通过 StoreKit 验证,可能存在安全风险。
error.localizedDescription 会显示未验证交易的具体错误原因。
4、hasTransactions 标志
var hasTransactions = false
如果 Transaction.currentEntitlements 发现有可恢复的交易,hasTransactions 会被设置为 true。
如果没有找到任何有效的交易,会打印:
print("没有找到任何有效的交易,可能是用户没有购买过任何项目")
5、交易处理结束的逻辑
如果 hasTransactions 仍然是 false,说明 没有有效的交易需要恢复,会提示:
"没有找到任何有效的交易,可能是用户没有购买过任何项目"
6、handleVerifiedTransaction(_ transaction:) 方法
transaction.productType 会返回购买的商品类型,常见的包括:
.nonConsumable:非消耗型商品(一次性购买,永久有效)。
.autoRenewable:自动续期订阅。
.nonRenewable:非续期订阅(例如:7 天会员)。
7、商品恢复逻辑
如果商品是非消耗型、自动续期订阅或非续期订阅:
savePurchasedState(for: transaction.productID)
savePurchasedState() 是自定义函数,用于存储商品已恢复的状态(通常保存在 UserDefaults或本地数据库中)。
8、savePurchasedState(for:) 方法
func savePurchasedState(for productID: String) {
// 保存购买状态(例如:存储在 UserDefaults 或 Keychain 中)
UserDefaults.standard.set(true, forKey: productID)
print("已恢复并存储商品状态:\(productID)")
}
常见问题和注意事项
只需要对非消耗型商品和订阅恢复购买,消耗型商品(如游戏内货币)无法恢复。
确保 restorePurchases() 逻辑中包含 Transaction.currentEntitlements 以获取所有当前有效的交易。
在 App Store Connect 中配置正确的 App 内购买项目(IAP) 并启用 StoreKit 权限。
总结
这篇文章实际上是《iOS通过StoreKit 2实现应用内购》文章的扩展,不理解的地方可能需要到前文进行查看。
最后的实现效果为,当没有内购时,点击“恢复内购”按钮,会输出“没有找到任何有效的交易,可能是用户没有购买过任何项目“,不会有其他的操作。
如果已经完成内购,点击“恢复内购“按钮后,就会调用savePurchasedState(for: transaction.productID)方法,恢复具体的内购商品。

参考文章
1、App 审核指南:https://developer.apple.com/cn/app-store/review/guidelines/
2、iOS通过StoreKit 2实现应用内购:https://fangjunyu.com/2024/05/30/ios%e5%ae%9e%e7%8e%b0%e5%ba%94%e7%94%a8%e5%86%85%e8%b4%ad/