I'm implementing a local package for subscriptions with StoreKit 2 but I'm struggling to get my app updated with my latest subscription status when it's expired of cancelled.
I'm starting my manager with this:
public func start(productIds: [String]) {
updates = observeTransactionUpdates()
Task {
do {
self.storeKitProducts = try await Product.products(for: productIds)
await updateUserSubscriptions()
} catch {
print("Error retrieving Products: \(error)")
}
}
}
Here it's the code where I'm making the purchase:
let result = try await storeKitProduct.purchase()
switch result {
case .success(.verified(let transaction)):
await updateUserSubscriptions()
await transaction.finish()
default:
break
}
And here I'm listening for updates (in case of renewal) like below:
private func observeTransactionUpdates() -> Task<Void, Error> {
Task(priority: .background) { [unowned self] in
for await result in Transaction.updates {
guard case .verified(let transaction) = result else { return }
await updateUserSubscriptions()
await transaction.finish()
}
}
}
Here is the implementation of updateUserSubscriptions():
private func updateUserSubscriptions() async {
var product: product?
for await result in Transaction.currentEntitlements {
guard case .verified(let transaction) = result else { continue }
if transaction.revocationDate == nil {
product = products.first(where: { $0.productID == transaction.productID })
}
await transaction.finish()
}
updateSubscribedProduct(product)
}
The updateSubscribedProduct method:
private func updateSubscribedProduct(_ subscribedProduct: Product?) {
self.subscribedProduct = subscribedProduct
addSubscriptionExpiration()
let previousIsSubscribed = isSubscribed
isSubscribed = subscribedProduct != nil
if previousIsSubscribed != isSubscribed {
subscriptionChanged.send(isSubscribed)
}
}
The issue is that if I am using the app, I cannot get updates of cancellation and/or expiration subscriptions automatically, without any user interaction.
As you can see, every time I assign a new purchased (or renewed) product, I add a subscription expiration loop, adding 30 seconds to guarantee the user gets their subscription features until the last second.
private func addSubscriptionExpiration() {
Task {
guard let product,
let result = await Transaction.currentEntitlement(for: product.productID),
case .verified(let transaction) = result,
let expirationDate = transaction.expirationDate else { return }
let newDate = expirationDate.addingTimeInterval(30)
timer = Timer(fire: newDate, interval: 0, repeats: false) { [weak self] _ in
guard let self else { return }
Task {
if await !self.isSubscriptionValid {
await self.updateUserSubscriptions()
}
}
}
RunLoop.main.add(timer!, forMode: .common)
}
}
I'm not happy with this implementation because it seems to be a poor workaround. Is there any better way to do so?
Thank you in advance!