Ale*_*nnd 4 storekit in-app-purchase app-store-connect swiftui storekit2
I'm trying to add/manage my first in-app purchase (non-consumable) on my iOS app and I just discovered that StoreKit 2 doesn't work well offline.
These past days, I used to display (or not) the premium features based on store.purchasedItems.isEmpty
but this doesn't work at all on Airplane mode.
I mean, I understand that some parts of my Store
file can't be accessible offline. The fetch request from the App Store can only works online, for example. But I didn't expected to be the same about the purchasedItems
.
So, I'm wondering what should I do instead? Maybe displaying (or not) the premium features based on an @AppStorage
variable? If yes, where should I toggle it? So many questions, I'm quite lost.
Here's my Store
file, it's a "light" version from the WWDC21 StoreKit 2 Demo:
import Foundation
import StoreKit
typealias Transaction = StoreKit.Transaction
public enum StoreError: Error {
case failedVerification
}
class Store: ObservableObject {
@Published private(set) var items: [Product]
@Published private(set) var purchasedItems: [Product] = []
var updateListenerTask: Task<Void, Error>? = nil
init() {
items = []
updateListenerTask = listenForTransactions()
Task {
await requestProducts()
await updateCustomerProductStatus()
}
}
deinit {
updateListenerTask?.cancel()
}
func listenForTransactions() -> Task<Void, Error> {
return Task.detached {
for await result in Transaction.updates {
do {
let transaction = try self.checkVerified(result)
await self.updateCustomerProductStatus()
await transaction.finish()
} catch {
print()
}
}
}
}
@MainActor
func requestProducts() async {
do {
let storeItems = try await Product.products(for: [ /* IAP */ ])
var newItems: [Product] = []
for item in storeItems {
newItems.append(item)
}
items = newItems
} catch {
print()
}
}
func purchase(_ product: Product) async throws -> Transaction? {
let result = try await product.purchase()
switch result {
case .success(let verification):
let transaction = try checkVerified(verification)
await updateCustomerProductStatus()
await transaction.finish()
return transaction
case .userCancelled, .pending:
return nil
default:
return nil
}
}
func isPurchased(_ product: Product) async throws -> Bool {
purchasedItems.contains(product)
}
func checkVerified<T>(_ result: VerificationResult<T>) throws -> T {
switch result {
case .verified(let safe):
return safe
case .unverified:
throw StoreError.failedVerification
}
}
@MainActor
func updateCustomerProductStatus() async {
var purchasedItems: [Product] = []
for await result in Transaction.currentEntitlements {
do {
let transaction = try checkVerified(result)
if let product = products.first(where: { $0.id == transaction.productID }) {
purchasedItems.append(product)
}
} catch {
print()
}
}
self.purchasedItems = purchasedItems
}
}
Run Code Online (Sandbox Code Playgroud)
我在使用 StoreKit 2 实现订阅时遇到了同样的问题。示例代码是面向对象的Product
,看起来访问这些对象的唯一方法是使用耗时的异步请求从 App Store 获取它们。我真的不想通过等待此请求完成来阻止我的应用程序的关键功能。
到目前为止,我发现保证离线访问客户购买状态的最佳方法是忽略示例代码并Transaction
使用Product
. 离线时,您仍然可以访问Transaction.currentEntitlements
. 不幸的是,currentEntitlements
仅包含有效订阅的交易,并且当订阅过期、撤销等时为空。
例如
@MainActor
func updateCustomerProductStatus() async {
var purchasedSubscriptions: [Transaction] = []
// Iterate through all of the user's purchased products.
for await result in Transaction.currentEntitlements {
do {
let transaction = try checkVerified(result)
switch transaction.productType {
case .autoRenewable:
purchasedSubscriptions.append(transaction)
default:
continue
}
} catch {
DLog("Failed to verify entitlement transaction")
}
}
self.purchasedSubscriptions = purchasedSubscriptions
}
// Use Transaction.productID to determine tier of service. e.g.
purchasedSubscriptions.first?.productID == "your.pro.identifier"
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
852 次 |
最近记录: |