The*_*heo 15 storekit in-app-purchase ios swift
我在 iOS 应用程序中提供订阅服务。如果应用程序在 iOS 15 或更高版本上运行,我会StoreKit 2用来处理订阅启动和续订。我的实现紧密遵循Apple 的示例代码。
我的一小部分用户 (<1%) 报告说,他们的有效订阅未被识别- 通常是在续订之后(开始新订阅似乎总是有效)。似乎没有显示任何用于续订的 StoreKit 交易。
经过一些故障排除后我发现:
AppStore.sync()没有帮助。我永远无法在我的设备上重现这个错误。
这是我的实现的要点:我有一个StoreManager类来处理与StoreKit. 初始化后,我立即迭代Transaction.all以获取用户完整的购买历史记录,并启动一个监听Transaction.updates. PurchasedItem是一个自定义结构,我用它来对有关交易的所有相关信息进行分组。购买的物品收集在字典中purchasedItems,我在字典中使用交易identifier作为键。对该字典的所有写入仅发生在绑定updatePurchasedItemFor()到MainActor.
class StoreManager {
static let shared = StoreManager()
private var updateListenerTask: Task<Void, Error>? = nil
init() {
updateListenerTask = listenForTransactions()
loadAllTransactions()
}
func loadAllTransactions() {
Task { @MainActor in
for await result in Transaction.all {
if let transaction = try? checkVerified(result) {
await updatePurchasedItemFor(transaction)
}
}
}
}
func listenForTransactions() -> Task<Void, Error> {
return Task(priority: .background) {
// Iterate through any transactions which didn't come from a direct call to `purchase()`.
for await result in Transaction.updates {
do {
let transaction = try self.checkVerified(result)
// Deliver content to the user.
await self.updatePurchasedItemFor(transaction)
// Always finish a transaction.
await transaction.finish()
} catch {
//StoreKit has a receipt it can read but it failed verification. Don't deliver content to the user.
Analytics.logError(error, forActivity: "Verification on transaction update")
}
}
}
private(set) var purchasedItems: [UInt64: PurchasedItem] = [:]
@MainActor
func updatePurchasedItemFor(_ transaction: Transaction) async {
let item = PurchasedItem(productId: transaction.productID,
originalPurchaseDate: transaction.originalPurchaseDate,
transactionId: transaction.id,
originalTransactionId: transaction.originalID,
expirationDate: transaction.expirationDate,
isInTrial: transaction.offerType == .introductory)
if transaction.revocationDate == nil {
// If the App Store has not revoked the transaction, add it to the list of `purchasedItems`.
purchasedItems[transaction.id] = item
} else {
// If the App Store has revoked this transaction, remove it from the list of `purchasedItems`.
purchasedItems[transaction.id] = nil
}
NotificationCenter.default.post(name: StoreManager.purchasesDidUpdateNotification, object: self)
}
private func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
// Check if the transaction passes StoreKit verification.
switch result {
case .unverified(_, let error):
// StoreKit has parsed the JWS but failed verification. Don't deliver content to the user.
throw error
case .verified(let safe):
// If the transaction is verified, unwrap and return it.
return safe
}
}
}
Run Code Online (Sandbox Code Playgroud)
为了查明用户是否订阅,我使用了这个简短的方法,在应用程序的其他地方实现:
var subscriberState: SubscriberState {
for (_, item) in StoreManager.shared.purchasedItems {
if let expirationDate = item.expirationDate,
expirationDate > Date() {
return .subscribed(expirationDate: expirationDate, isInTrial: item.isInTrial)
}
}
return .notSubscribed
}
Run Code Online (Sandbox Code Playgroud)
所有这些代码对我来说看起来非常简单,并且与 Apple 的示例代码非常相似。尽管如此,某个地方还是有一个错误,我找不到它。
我可以想象这是以下三个问题之一:
identifier,我可以将其用作将其收集到字典中的密钥。为了排除 3.,我已向 Apple 提交了 TSI 请求。他们的回应本质上是:您应该使用Transaction.currentEntitlements而不是Transaction.all确定用户当前的订阅状态,但实际上这个实现也应该有效。如果没有,请提交错误。
我之所以使用它,Transaction.all是因为我需要用户的完整交易历史记录来自定义应用程序中的消息传递和特别优惠,而不仅仅是确定用户是否有有效订阅。所以我提交了一个错误,但还没有收到任何回复。
通过分析从大量用户收集了大量低级事件后,我非常确信这是由 StoreKit 2 中的错误引起的,并且
Transaction.all无法可靠地返回所有旧的、已完成的订阅开始和续订交易。我通过收集循环中的所有事务标识符for await result in Transaction.all,然后将这些标识符作为单个事件发送到我的分析后端来检查这一点。我可以清楚地看到,对于某些用户来说,先前存在的标识符有时会在随后启动应用程序时丢失。
不幸的是,苹果从 TSI 得到的唯一建议就是报告错误,而苹果从未对我的详细错误报告做出回应。*
作为解决方法,我现在将所有事务缓存在磁盘上,并在启动后将缓存的事务与来自Transaction.all和的所有新事务合并Transaction.updates。
这工作完美 - 自从我实现以来,我没有收到客户关于无法识别的订阅的任何投诉。
* 弄清楚这一切并找到可靠的修复方法花了几个月的时间 - 我很高兴 Apple 只用了我收入的 30% 就提供了如此出色、可靠的服务,tysm。
| 归档时间: |
|
| 查看次数: |
2129 次 |
| 最近记录: |