How do I get Transaction.currentEntitlements
to return subscription auto-renewal transactions that happen while the app is closed?
I have implemented auto-renewing subscriptions in my macOS app.
I have a StoreManager
that creates listeners on Transaction.currentEntitlements
and Transaction.updates
If the subscription renews while the app is running, Transaction.updates
receives the latest transactions, as expected.
However, When a renewal happens automatically while the app is not running, when the app is re-opened the Transaction.currentEntitlements
does not return the renewal transaction.
Following the steps to reproduce:
- Start the app and purchase an auto-renewing subscription
- kill the app
- wait for the subscription to expire and auto renew (monthly can be set to renew every 30 seconds when testing)
- restart the app
Transaction.currentEntitlements
returns nothing.
Checking the StoreKit Transactions debugging window when the app starts, renewal transactions that happened while the app was closed show as "not finished".
The non-finished transactions are the ones not returned.
If the subscription again gets renewed while the app is running, the listener on Transaction.updates
gets fired and the new renewal plus any non-finished transactions are returned.
Here's the relevant code from the StoreManager
@MainActor
class StoreManager: ObservableObject {
var updates: Task<Void, Never>? = nil
var transactions: Task<Void, Never>? = nil
init() {
updates = updatesListenerTask()
transactions = transactionsListenerTask()
}
@Published private (set) var transaction: StoreKit.Transaction?
func updatesListenerTask() -> Task<Void, Never> {
Task.detached(priority: .background) {
for await result in Transaction.updates {
Task { @MainActor in
do {
let transaction = try self.validateResult(result)
self.transaction = transaction
await transaction.finish()
} catch {
print("Failed verification =\(error.localizedDescription)")
}
}
}
}
}
func transactionsListenerTask() -> Task<Void, Never> {
Task.detached(priority: .background) {
for await result in Transaction.currentEntitlements {
Task { @MainActor in
do {
let transaction = try self.validateResult(result)
self.transaction = transaction
await transaction.finish()
} catch {
print("Failed verification =\(error.localizedDescription)")
}
}
}
}
}
func validateResult(_ result: VerificationResult<StoreKit.Transaction>) throws -> StoreKit.Transaction {
switch result {
case let .verified(transaction):
return transaction
case .unverified:
throw StoreManagerError.failedVerification
}
}
}
Update
Transaction.latest(for: <productId>)
returns the latest finished product, so not the auto renewed one.