Transaction.updates sending me duplicated transactions after marking finished()

Hey y'all,

I'm reaching out because of an observed issue that I am experience both in sandbox and in production environments. This issue does not occur when using the Local StoreKit configurations.

For context, my app only implements auto-renewing subscriptions. I'm trying to track with my own analytics every time a successful purchase is made, whether in the app or externally through Subscription Settings. I'm seeming too many events for just one purchase.

My app is observing Transaction.updates. When I make a purchase with Product.purchase(_:), I successfully handle the purchase result. After about 10-20 seconds, I receive 2-3 new transactions in my Transaction.updates, even though I already handled and finished the Purchase result. This happens on production, where renewals are one week. This also happens in Sandbox, where at minimum renewals are every 3 minutes.

The transactions do not differ in transactionId, revocationDate, expirationDate, nor isUpgraded... so not sure why they're coming in through Transaction.updates if there are no "updates" to be processing.

For purchases made outside the app, I get the same issue. Transaction gets handled in updates several times.

Note that this is not an issue if a subscription renews. I use `Transaction.reason


I want to assume that StoreKit is a perfect API and can do no wrong (I know, a poor assumption but hear me out)... so where am I going wrong?

My current thought is a Swift concurrency issue. This is a contrived example:

// Assume Task is on MainActor
Task(priority: .background) { @MainActor in
  for await result in Transaction.updates in {
    // We suspend current process,
    // so will we go to next item in the `for-await-in` loop?
    // Because we didn't finish the first transaction, will we see it again in the updates queue?
    await self.handle(result)
  }
}

@MainActor
func handle(result) async {
  ...
  await Analytics.sendEvent("purchase_success")
  transaction.finish()
}

Thanks for your post as it seems very well written explaining the issue with the code in the end, but following your code as you just looking at a subscription is difficult to understand what is the cause of the issue.

I’m not an expert in subscriptions but I wonder looking at your code if could be a Swift's concurrency issue as you are not setting the for await result in Transaction.updates ?

I think the problem may b3e that your current handler, though running on @MainActor, processes transactions sequentially within that single loop. If processing takes even a few milliseconds, new updates can arrive and pile up behind the currently processing one, potentially leading to them being handled more than once if logic isn't airtight?

I do believe by encapsulating your logic within an actor and using a set for deduplication, you create a predictable and thread-safe environment for handling StoreKit's asynchronous transaction updates, effectively solving the duplicate problem you've encountered.

Something like this?

// No good code, just the idea to add the await on the handler. The code is not from Xcode, wrote it by hand from my simple mind:

.addTask {
                    await self.handle(transaction)
                }

Albert Pascual
  Worldwide Developer Relations.

Transaction.updates sending me duplicated transactions after marking finished()
 
 
Q