Why doesn’t Transaction.updates emit reliably?

I'm on macOS Sequoia Version 15.7.3 (24G419) and using Xcode Version 26.2 (17C52).

In my Xcode project, Transaction.updates and Product.SubscriptionInfo.Status.updates don’t seem to emit updates reliably.

The code below works consistently in a fresh Xcode project using a minimal setup with a local StoreKit Configuration file containing a single auto-renewable subscription.

class InAppPurchaseManager {
    static let shared = InAppPurchaseManager()

    var transactionTask: Task<Void, Never>?
    var subscriptionTask: Task<Void, Never>?

    init() {
        print("Launched InAppPurchaseManager...")

        transactionTask = Task(priority: .background) {
            for await result in Transaction.updates {
                print("\nReceived transaction update...")
                try? await result.payloadValue.finish()
            }
        }

        subscriptionTask = Task(priority: .background) {
            for await result in Product.SubscriptionInfo.Status.updates {
                print("\nReceived subscription update...")
                print("state:", result.state.localizedDescription)
            }
        }
    }

}

I initialise it in:

func applicationDidFinishLaunching(_ aNotification: Notification) {
    _ = InAppPurchaseManager.shared
}

I do not build any UI for this test. I open StoreKit Transaction Manager then click Create Transaction → select the product → choose Purchase (Default)NextDone. The console shows that it detects the initial purchase, renewals and finishes each transaction.

It also works even if I do not add the In-App Purchase capability.

In my actual project, the initial purchase is detected and finished, but renewals are not detected. Subsequent transactions then appear as unverified, presumably because the updates are not being observed so the transactions are not being finished.

What can I do to make this work reliably in my actual project?

For context, in the actual project:

  • I have a StoreKit Configuration file that is synced with App Store Connect
  • The In-App Purchase capability is enabled
  • The configuration file is selected in the scheme
  • The products in App Store Connect show “Ready to Submit”
  • Loading products works:
try await Product.products(for: ...)

Also, I use ProductView for the purchase UI. The first purchase works and is detected and finished, but subsequent renewals are not finished because the updates do not seem to be emitted.

Update the customer product status using

for await result in Transaction.currentEntitlements {
    // Verify transaction and append to the product items/subscription items.
}

Also its better to use @Observable class on your InAppPurchaseManager instead of a shared singleton, especially if your project is iOS 17+.

You would have:

var purchasedProducts: [Product] = []
var purchasedSubscriptions: [Product] = []

Check out tutorials online on how to create Observable StoreKit 2 manager.

Sorry, how would using the @Observable macro help in this case?

As I mentioned in my post, the InAppPurchaseManager works perfectly in a fresh Xcode project with a minimal setup.

Your listenerTask will update the purchasedProducts/purchasedSubscriptions. If there is any change in the Transaction.updates, you can update the array.

With Observable, your views will update accordingly to this change.

Take a look at the WWDC25 sample on Understanding StoreKit workflows.

https://developer.apple.com/documentation/StoreKit/understanding-storekit-workflows

My app is a Mac app that uses the AppKit framework. It is not a SwiftUI app, nor is it an iOS app. It uses SwiftUI for some views.

The sample project code is similar to my code snippet but is built using the SwiftUI design pattern.

I want Transaction.updates and Product.SubscriptionInfo.Status.updates to work, but they are not detecting the changes I make when I make purchases, nor are they detecting purchases when using the Xcode Transaction Manager.

Also, I use subscriptionStatusTask(for:priority:action:) modifier where I use ProductView and it doesn't work. It is supposed to react in real time. It works if I reload the view by switching to another view and then back.

I believe it is less to do with the code and is something else. Maybe the Xcode project file settings is messed up or I am missing something. I tried restarting the Mac and clearing the derived data.

Likewise, as I mentioned, it works perfectly in a fresh Xcode project with a minimal setup, but it doesn’t work in my project. Why?

I have run some tests on my copy of StoreKit testing project. When the app is not active, and a renewal comes through, it will be unfinished. The listener task you have in Transaction.updates might not catch it.

Question: Does your app handle unfinished transactions?

You need to listen for Transaction.unfinished, and finish any verified transactions. Either call it on the next launch, or create another Task to listen for unfinished.

func processUnfinishedTransactions() async {
        for await result in Transaction.unfinished {
            switch result {
            case .verified(let verified):
                // Always finish verified transactions
                await verified.finish()
                
            case .unverified(let unverified, _):
                // Handle unverified
                
            }
        }
    }

If you check the Understanding StoreKit Workflows sample from WWDC25, notice they have 3 things going on in the Store.swift file:

Task(priority: .background) {
            // Finish any unfinished transactions -- for example, if the app was terminated before finishing a transaction.
            for await verificationResult in Transaction.unfinished {
                await handle(updatedTransaction: verificationResult)
            }

            // Fetch current entitlements for all product types except consumables.
            for await verificationResult in Transaction.currentEntitlements {
                await handle(updatedTransaction: verificationResult)
            }
        }
        Task(priority: .background) {
            for await verificationResult in Transaction.updates {
                await handle(updatedTransaction: verificationResult)
            }
        }

Although it uses @Observable, your shared singleton should be able to keep these tasks alive. The same principles apply.

It sounds like you tested it on iOS. I understand that some developers refresh Transaction.currentEntitlements when the iOS app switches to the foreground.

Yes, I do handle Transaction.unfinished and it is not related to my issue.

Have you seen this post by @saxmanbob? He had a similar issue to mine, but he resolved it due to some “Objective-C machinery”. I don’t use Objective-C in my app, although it may exist within some Swift packages.

Please try the following and let me know if you get the same result. Download the CotEditor Xcode project, copy and paste the InAppPurchaseManager code somewhere appropriate and launch it in applicationDidFinishLaunching(_:).

Locate the CotEditor.storekit file and change the Subscription Renewal Rate to Any Renewal Every 10 Seconds or Any Renewal Every 2 Seconds.

Run the app and observe the console. You will see that it does not behave as expected.

You could also try this in a fresh AppKit Xcode project. You don’t need to build the UI and you will see that it works perfectly.

What if you check Transaction.currentEntitlements on every NSApplication.willBecomeActiveNotification or NSApplication.didBecomeActiveNotification, does the renewal transaction get reflected?

Yes, it refreshes Transaction.updates and Product.SubscriptionInfo.Status.updates.

I added the following method to InAppPurchaseManager:

func refresh() {
    Task {
        for await result in Transaction.currentEntitlements {
            print("\nReceived current entitlements update...")

            try? await result.payloadValue.finish()
        }
    }
}

And called it in applicationWillBecomeActive(_:):

func applicationWillBecomeActive(_ notification: Notification) {
    InAppPurchaseManager.shared.refresh()
}

I have some information.

I downloaded Xcode 26.3 Release Candidate (17C519) from the Apple Developer website.

I used the VirtualBuddy app and installed macOS Tahoe 26.3.

I then opened the Xcode project and configured the StoreKit Configuration file (synchronised with App Store Connect), setting the subscription renewal rate to Any Renewal Every 10 Seconds.

I ran the app, made a subscription purchase and observed that my in-app purchase handling code successfully completed the transaction. I also logged Transaction.updates and Product.SubscriptionInfo.Status.updates and everything worked as expected.

However, I do not wish to upgrade to macOS Tahoe just yet.

Why doesn’t Transaction.updates emit reliably?
 
 
Q