Listen to cancellation/expiration auto-renew Subscriptions with StoreKit 2

192 Views Asked by At

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!

0

There are 0 best solutions below