StoreKit

RSS for tag

Support in-app purchases and interactions with the App Store using StoreKit.

StoreKit Documentation

Posts under StoreKit subtopic

Post

Replies

Boosts

Views

Activity

iOS 26 RC: Testflight showing wrong currency for sandbox accounts
My app has in app purchase for subscriptions, available in many countries. When using Sandbox App Store accounts on TestFlight with a locale different from my own in the iOS 26 RC, I'm getting incorrect currency coming back from Product.products(for: identifiers), and so my app displays the wrong price for the locale. However, the actual Apple Pay buy sheet shows the proper currency symbol and currency amount. This did not happen on prior versions of iOS. Is anyone else experiencing this?
1
1
300
Jan ’26
StoreKit2: appAccountToken in purchase() always returns first value instead of updated UUID
Hello, I’m experiencing an issue with StoreKit 2 when passing a new appAccountToken for each purchase request. Case-ID: 15948169 (for DTS reference) Description of the Problem When initiating a purchase, I generate a new UUID to use as the appAccountToken: let serverTransactionId = UUID() let options: Set<Product.PurchaseOption> = [ .appAccountToken(serverTransactionId) ] let result = try await product.purchase(options: options) Expected Behavior: Each new purchase should return the updated appAccountToken that I pass into the purchase options. Actual Behavior: The payload response after success always contains the same appAccountToken from the very first transaction. It ignores subsequent UUIDs I pass and keeps reusing the original one. This causes issues because the same identifier is being reused across multiple transactions, making it difficult to map purchases to the correct user session. Steps to Reproduce Generate a fresh UUID using UUID(). Pass it as .appAccountToken when calling purchase(). Complete the transaction in the sandbox environment. Inspect the payload response → The appAccountToken value is always the same as the first one used, not the newly provided one. Additional Info I do have a focused test project that reproduces this issue. The issue appears specific to appAccountToken persistence across multiple transactions. Has anyone else experienced this behavior with StoreKit 2? Is this expected (Apple caching the first token) or could this be a bug?
3
1
426
Nov ’25
InvalidRequestError on iOS 18.4
We’ve recently encountered an increased rate of purchase errors StoreKit.InvalidRequestError error 1 (https://developer.apple.com/documentation/storekit/invalidrequesterror) specifically on iOS 18.4. We suspect this might be related to the new Advanced Commerce API introduced by Apple, although we haven’t made any changes related to this API in our app. Have you experienced similar issues since the release of iOS 18.4 or when integrating the Advanced Commerce API? Any insights or suggestions would be greatly appreciated. Thanks!
1
1
324
May ’25
StoreKit 2 - Is it necessary to finish unverified transactions?
The sample code provided in https://developer.apple.com/wwdc21/10114 doesn't appear to call finish() on unverified transactions, and I haven't been able to find any documentation regarding what to do with unfinished transactions. However, Apple has always emphasized the importance of finishing transactions, and since a transaction object is provided even with the unverified state, I'd love some guidance!
4
1
2.8k
May ’25
Can SKOverlay be used to prompt updates for the same app?
According to Apple's documentation, SKOverlay is designed to recommend other applications to users. I'm seeking clarification on whether it also supports displaying update prompts for the host application itself. Use case: My app (for example, HelloDeveloper) is live at version 2.0, but some users are still on version 1.0. I want to display a soft update prompt that allows users to remain in the app. Question: Is it possible to use SKOverlay with my app's App Store ID to present an update option without requiring users to leave the app?
1
0
191
Oct ’25
how is the refund amount calculated for auto-renewing upgrade?
Hi, We have a app with some auto-renewing subscription in a group of subscriptions. When a user upgrade from a subscription to another, the "user receive a refund of the prorated amount of their original subscription" (https://developer.apple.com/app-store/subscriptions/). How is the prorated calculated ? Example : subscription to 14,99$ / month. If subscriber upgrade after 10 days, is the refund calculated 10/30 of 14,99$ (so ~5$) ?
0
1
96
Sep ’25
transaction.subscriptionStatus (TestFlight) returns nil on iOS 26 (works from Xcode & on iOS 18)
I’m seeing an issue with subscriptions in TestFlight builds on iOS 26. Running from Xcode works as expected, and the App Store build looks fine. But when I install the same build via TestFlight, transaction.subscriptionStatus is nil. The identical binary behaves correctly on an iOS 18 device. Is this expected behavior on iOS 26 TestFlight, or am I missing something? Thanks!
1
1
226
Oct ’25
SKAN / AdAttributionKit Development Postback Not Triggering
We’re testing SKAN postbacks via AdAttributionKit but aren’t receiving any requests on our server even after generating development impressions and triggering a postback. Setup: Domain: https://linkrunner-skan.com Configured in Info.plist as: <key>NSAdvertisingAttributionReportEndpoint</key> <string>https://linkrunner-skan.com</string> <key>AttributionCopyEndpoint</key> <string>https://linkrunner-skan.com</string> Apple automatically appends the .well-known paths: /.well-known/private-click-measurement/report-attribution/ /.well-known/skadnetwork/report-attribution/ ATS diagnostics for the domain: PASS for all tests (TLS 1.0–1.3, PFS disabled, arbitrary loads allowed, etc.) Both .well-known paths are publicly accessible and return 200 OK Testing Flow: Enabled Developer → AdAttributionKit Developer Mode on iOS (15+) Followed Apple’s official guide: Testing AdAttributionKit with Developer Mode Generated test impression using: createAdAttributionKitDevelopmentImpression implemented in SKANManager.swift Called Postback.updateConversionValue with lockPostback: true Created Development Postback from Developer Settings Waited 30+ minutes while intercepting server requests (proxy + backend logs) What We’ve Tried So Far: Confirmed ATS compliance with nscurl --ats-diagnostics (all PASS) Verified .well-known paths are accessible publicly without redirects Tested endpoints manually with a POST request – server responds 200 OK Confirmed Info.plist entries exactly match Apple’s required keys Double-checked iOS device is running iOS 15+ with Developer Mode enabled Repeated test flow multiple times with fresh impressions and postbacks Waited up to 1 hour for postback (in case of delays) Issue: No POST requests are being received from Apple to either .well-known endpoint, even though the setup appears correct and ATS tests pass. References Used: Configuring an Advertised App Generating JWS Impressions Question: Has anyone faced a similar issue where AdAttributionKit Development Postbacks are not firing despite correct Info.plist setup, ATS compliance, and reachable .well-known endpoints? Any insight into possible missing configuration steps or testing nuances would be greatly appreciated.
0
1
106
Aug ’25
Storekit configuration broken in Xcode 16.4 the file has been changed
Hi everyone, After updating to Xcode 16.4, my StoreKit configuration stopped working. Whenever I run the app with a .storekit file set as the active scheme, I immediately get this alert: “The file has been changed. Do you want to save your changes or revert to the file on disk?” No matter if I choose Save Anyway or Revert, StoreKit testing does not work - the purchases are not simulated, and the scheme is basically broken. This issue didn’t exist in Xcode 15.4 - the same StoreKit configuration file works fine there. What I tried so far: Clearing Derived Data - no effect Making sure no scripts/tools modify the .storekit file - still happens Restarting Xcode and macOS - no change Environment: Xcode 16.4 Happens in both Simulator and on device Reproducible 100% Has anyone else seen this in 16.4? Any known workarounds until Apple fixes it? Thanks!
5
1
489
Oct ’25
StoreKit 2 not loading subscription products
Hi everyone, I’m seeing a strange behavior with StoreKit 2 and I’d like to know if anyone else experienced this. My subscription group “ROTA Premium” (Monthly + Annual) is currently Waiting for Review in App Store Connect. What works In Xcode’s StoreKit sandbox, everything loads correctly: Products appear Trial starts Purchases work What doesn’t work In TestFlight and App Review, StoreKit 2 returns zero products, so my paywall shows: “No subscription options found.” There are: No geo restrictions No backend No VPN/IP filtering Paid Apps Agreement is accepted App Review said the device was online, but couldn’t give technical help. My question Has anyone seen StoreKit 2 fail to load subscription products when the subscription group is still in Waiting for Review? Do subscription groups need to be reviewed together with the app version for StoreKit 2 to return them in TestFlight/App Review? Any advice would be appreciated! Thanks.
0
1
92
Nov ’25
When is the unverified branch of AppTransaction.shared entered?
Hi all, I am adding the following StoreKit 2 code to my app, and I don't see anything in Apple's documentation that explains the unverified case. When is that case exercised? Is it when someone has tampered with the app receipt? Or is it for more mundane things like poor network connectivity? // Apple's docstring on `shared` states: // If your app fails to get an AppTransaction by accessing the shared property, see refresh(). // Source: https://developer.apple.com/documentation/storekit/apptransaction/shared var appTransaction: VerificationResult<AppTransaction>? do { appTransaction = try await AppTransaction.shared } catch { appTransaction = try? await AppTransaction.refresh() } guard let appTransaction = appTransaction else { AppLogger.error("Couldn't get the app store transaction") return false } switch appTransaction { case .unverified(appTransaction, verificationError): // For what reasons should I expect this branch to be entered in production? return await inspectAppTransaction(appTransaction, verifiedByApple: false) case .verified(let appTransaction): return await inspectAppTransaction(appTransaction, verifiedByApple: true) } Thank you, Lou
12
1
864
Apr ’25
StoreKit 2: Delayed Transaction and Entitlement Updates After Promo Code Subscription Redemption
I’m implementing a subscription purchase flow using promo code redemption via an external App Store URL. Flow: User taps “Purchase” in the app (spinner shown) App opens the promo redemption URL (apps.apple.com/redeem) User completes redemption in the App Store User returns to the app The app must determine whether the subscription was purchased within a reasonable time window The app listens to Transaction.updates and also checks Transaction.currentEntitlements when the app returns to the foreground. Issue: After redeeming a subscription promo code via the App Store and returning to the app, the app cannot reliably determine whether the subscription was successfully purchased within a short, user-acceptable time window. In many cases, neither Transaction.updates nor Transaction.currentEntitlements reflects the newly redeemed subscription immediately after returning to the app. The entitlement may appear only after a significant delay, or not within a 60-second timeout at all, even though the promo code redemption succeeded. Expected: When the user returns to the app after completing promo code redemption, StoreKit 2 should report the updated subscription entitlement shortly thereafter (e.g. within a few seconds) via either Transaction.updates or Transaction.currentEntitlements. Below is the minimal interactor used in the sample project. The app considers the purchase successful if either a verified transaction for the product is received via Transaction.updates, or the product appears in Transaction.currentEntitlements when the app returns to the foreground. Otherwise, the flow fails after a 60-second timeout. Questions: Is this entitlement propagation delay expected when redeeming promo codes through the App Store? Is there a recommended API or flow for immediately determining whether a subscription has been successfully redeemed? Is there a more reliable way to detect entitlement changes after promo code redemption without triggering user authentication prompts (e.g., from AppStore.sync())? import UIKit import StoreKit final class PromoPurchaseInteractor { private let timeout: TimeInterval = 60 private struct PendingOfferRedemption { let productId: String let completion: (Result<Bool, Error>) -> Void } private var pendingRedemption: PendingOfferRedemption? private var updatesTask: Task<Void, Never>? private var timeoutTask: Task<Void, Never>? enum DefaultError: Error { case generic case timeout } init() { NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil) } deinit { NotificationCenter.default.removeObserver(self) updatesTask?.cancel() timeoutTask?.cancel() } func purchaseProduct(using offerUrl: URL, productId: String, completion: @escaping (Result<Bool, Error>) -> Void) { guard pendingRedemption == nil else { completion(.failure(DefaultError.generic)) return } pendingRedemption = PendingOfferRedemption(productId: productId, completion: completion) startPurchase(using: offerUrl) } @objc private func willEnterForeground() { guard let pendingRedemption = pendingRedemption else { return } startTimeoutObserver() Task { if await hasEntitlement(for: pendingRedemption.productId) { await MainActor.run { self.completePurchase(result: .success(true)) } } } } private func startPurchase(using offerURL: URL) { startTransactionUpdatesObserver() UIApplication.shared.open(offerURL) { [weak self] success in guard let self = self else { return } if !success { self.completePurchase(result: .failure(DefaultError.generic)) } } } private func completePurchase(result: Result<Bool, Error>) { stopTransactionUpdatesObserver() stopTimeoutObserver() guard let _ = pendingRedemption else { return } pendingRedemption?.completion(result) pendingRedemption = nil } private func startTransactionUpdatesObserver() { updatesTask?.cancel() updatesTask = Task { for await update in Transaction.updates { guard case .verified(let transaction) = update else { continue } await MainActor.run { [weak self] in guard let self = self, let pending = self.pendingRedemption, transaction.productID == pending.productId else { return } self.completePurchase(result: .success(true)) } await transaction.finish() } } } private func stopTransactionUpdatesObserver() { updatesTask?.cancel() updatesTask = nil } private func startTimeoutObserver() { guard pendingRedemption != nil else { return } timeoutTask?.cancel() timeoutTask = Task { try? await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000)) await MainActor.run { [weak self] in self?.completePurchase(result: .failure(DefaultError.timeout)) } } } private func stopTimeoutObserver() { timeoutTask?.cancel() timeoutTask = nil } private func hasEntitlement(for productId: String) async -> Bool { for await result in Transaction.currentEntitlements { guard case .verified(let transaction) = result else { continue } if transaction.productID == productId { return true } } return false } }
1
1
206
Jan ’26
External Link Account API - ExternalLinkAccount.canOpen() always return false
This is a hybrid app built with JavaScript (Vue) + Capacitor. It is a reader app and has been authorized by Apple to use the External Link Account Entitlement, allowing users to manage their subscriptions outside of the app. I have implemented the External Link Account API. When I click on "Gerenciar Assinatura em...", I use the External Link Account API to check if the modal is available (using ExternalLinkAccount.canOpen()). I always get "false". my plugin in swift: my app: I believe this is due to the fact that I am in a development environment. My project is configured correctly in the following files: info.plist and App.entitlements. I also have the authorization in my profile visible in Xcode. I have attached screenshots for validation. The question is: should the External Link Account API work in a test environment? I am testing the build in Xcode with a physical iPhone with iOS 18. file info.plist: file App.entitlements: xcode with authorization in my profile: If you could let me know if I am doing something wrong, I would greatly appreciate it.
2
0
159
Jun ’25
StoreKit 2: Handle unfinished consumables
I have non-consumable and consumable in-app purchases in my app. The tutorial I was following stated Transaction.currentEntitlements includes unfinished consumables, which is incorrect according to the documentation. Is the correct way to handle unfinished consumables (and non-consumables) to implement Transaction.updates and call finish() if it’s verified? The documentation says that listener will receive unfinished transactions once upon app launch, so with that, do I understand correctly you do not need to implement Transaction.unfinished unless you want to look for unfinished transactions manually later on? Otherwise what is the correct and most recommended way to handle unfinished consumables? Is there a way to test that scenario in Xcode?
1
0
263
Jun ’25
Consolidate subscriptions between apps
Because of historical reasons we have the same app with different bundle identifiers in different App Stores, e. g. one in Germany and one in Poland and one in France. Every app offers a monthly and a yearly in app subscription. We want to consolidate our apps and user base and want to have only one app in the end. The question is now: Can Apple make the monthly/daily subscriptions from e. g. the app in the store in Poland available or migrate them to the german app in the german App Store when we make the "german" app available also in Poland? The final goal would be that a user who bought a subscription in the polish store would have the subscription also available on his Apple ID in the german store. Is this possible? Can Apple make this possible?
1
0
74
Jun ’25
Issue that subscription products (monthly subscription and annual subscription) cannot be obtained from App Store Connect
Dear my friends, I have set up two subscriptions (monthly and annual) in App Store Connect. By configuring with StoreKit, I can import the configurations from App Store Connect into the local .storekit file, and the program can run normally. However, when the StoreKit configuration is set to NONE, I am unable to retrieve product information from App Store Connect. The code for fetching the product list is as follows. Could you please provide suggestions on how to proceed? Thank you very much! private let productIds: [String] = ["subscription.year", "subscription.monthly"] subscriptions = try await Product.products(for: productIds)
2
0
138
Jun ’25
In-App Purchase products suddenly not returned by queryProductDetails
Hi, We're currently experiencing an issue with consumable In-App Purchases on our production iOS app. Until the end of May, everything was working as expected, but starting in early June, our app no longer receives any products when calling queryProductDetails() using Flutter’s in_app_purchase plugin (which utilizes StoreKit). Here’s what we’ve confirmed so far: The product IDs are correctly configured in App Store Connect, and all items are marked as “Approved.” No recent changes have been made to the bundle ID or the product IDs. The “Base Territory” setting was updated for each IAP item in early May. After that change, product retrieval and purchases were working normally through the end of May. This issue is happening on real devices in production, and multiple users are affected. The same functionality continues to work correctly on Android. All requested product IDs are being returned in the notFoundIDs list of the queryProductDetails() response. We're quite puzzled by this issue as no clear cause has been identified so far. Any thoughts on this issue would be much appreciated. Thank you!
1
0
179
Jun ’25
Why Non-Consumable product has originalTransactionId?
I try to call Get Transaction Info from App Store Server API, and the transactionId is for a Non-consumable type product, but it is odd that there are so many different transactionId and they have a same originalTransactionId { "bundleId": "${bundleId}", "environment": "Production", "inAppOwnershipType": "PURCHASED", "originalPurchaseDate": 1691220528000, "originalTransactionId": "${originalTransactionId}", "productId": "${productId}", "purchaseDate": 1691220528000, "quantity": 1, "signedDate": 1692590989925, "storefront": "USA", "storefrontId": "143441", "transactionId": "${originalTransactionId}", "transactionReason": "PURCHASE", "type": "Non-Consumable" } the defination of Non-Consumable is can only purchase once for same apple account. But why there would have originalTransactionId?
3
0
1.2k
May ’25
iOS 26 RC: Testflight showing wrong currency for sandbox accounts
My app has in app purchase for subscriptions, available in many countries. When using Sandbox App Store accounts on TestFlight with a locale different from my own in the iOS 26 RC, I'm getting incorrect currency coming back from Product.products(for: identifiers), and so my app displays the wrong price for the locale. However, the actual Apple Pay buy sheet shows the proper currency symbol and currency amount. This did not happen on prior versions of iOS. Is anyone else experiencing this?
Replies
1
Boosts
1
Views
300
Activity
Jan ’26
StoreKit2: appAccountToken in purchase() always returns first value instead of updated UUID
Hello, I’m experiencing an issue with StoreKit 2 when passing a new appAccountToken for each purchase request. Case-ID: 15948169 (for DTS reference) Description of the Problem When initiating a purchase, I generate a new UUID to use as the appAccountToken: let serverTransactionId = UUID() let options: Set<Product.PurchaseOption> = [ .appAccountToken(serverTransactionId) ] let result = try await product.purchase(options: options) Expected Behavior: Each new purchase should return the updated appAccountToken that I pass into the purchase options. Actual Behavior: The payload response after success always contains the same appAccountToken from the very first transaction. It ignores subsequent UUIDs I pass and keeps reusing the original one. This causes issues because the same identifier is being reused across multiple transactions, making it difficult to map purchases to the correct user session. Steps to Reproduce Generate a fresh UUID using UUID(). Pass it as .appAccountToken when calling purchase(). Complete the transaction in the sandbox environment. Inspect the payload response → The appAccountToken value is always the same as the first one used, not the newly provided one. Additional Info I do have a focused test project that reproduces this issue. The issue appears specific to appAccountToken persistence across multiple transactions. Has anyone else experienced this behavior with StoreKit 2? Is this expected (Apple caching the first token) or could this be a bug?
Replies
3
Boosts
1
Views
426
Activity
Nov ’25
InvalidRequestError on iOS 18.4
We’ve recently encountered an increased rate of purchase errors StoreKit.InvalidRequestError error 1 (https://developer.apple.com/documentation/storekit/invalidrequesterror) specifically on iOS 18.4. We suspect this might be related to the new Advanced Commerce API introduced by Apple, although we haven’t made any changes related to this API in our app. Have you experienced similar issues since the release of iOS 18.4 or when integrating the Advanced Commerce API? Any insights or suggestions would be greatly appreciated. Thanks!
Replies
1
Boosts
1
Views
324
Activity
May ’25
StoreKit 2 - Is it necessary to finish unverified transactions?
The sample code provided in https://developer.apple.com/wwdc21/10114 doesn't appear to call finish() on unverified transactions, and I haven't been able to find any documentation regarding what to do with unfinished transactions. However, Apple has always emphasized the importance of finishing transactions, and since a transaction object is provided even with the unverified state, I'd love some guidance!
Replies
4
Boosts
1
Views
2.8k
Activity
May ’25
Can SKOverlay be used to prompt updates for the same app?
According to Apple's documentation, SKOverlay is designed to recommend other applications to users. I'm seeking clarification on whether it also supports displaying update prompts for the host application itself. Use case: My app (for example, HelloDeveloper) is live at version 2.0, but some users are still on version 1.0. I want to display a soft update prompt that allows users to remain in the app. Question: Is it possible to use SKOverlay with my app's App Store ID to present an update option without requiring users to leave the app?
Replies
1
Boosts
0
Views
191
Activity
Oct ’25
how is the refund amount calculated for auto-renewing upgrade?
Hi, We have a app with some auto-renewing subscription in a group of subscriptions. When a user upgrade from a subscription to another, the "user receive a refund of the prorated amount of their original subscription" (https://developer.apple.com/app-store/subscriptions/). How is the prorated calculated ? Example : subscription to 14,99$ / month. If subscriber upgrade after 10 days, is the refund calculated 10/30 of 14,99$ (so ~5$) ?
Replies
0
Boosts
1
Views
96
Activity
Sep ’25
transaction.subscriptionStatus (TestFlight) returns nil on iOS 26 (works from Xcode & on iOS 18)
I’m seeing an issue with subscriptions in TestFlight builds on iOS 26. Running from Xcode works as expected, and the App Store build looks fine. But when I install the same build via TestFlight, transaction.subscriptionStatus is nil. The identical binary behaves correctly on an iOS 18 device. Is this expected behavior on iOS 26 TestFlight, or am I missing something? Thanks!
Replies
1
Boosts
1
Views
226
Activity
Oct ’25
SKAN / AdAttributionKit Development Postback Not Triggering
We’re testing SKAN postbacks via AdAttributionKit but aren’t receiving any requests on our server even after generating development impressions and triggering a postback. Setup: Domain: https://linkrunner-skan.com Configured in Info.plist as: <key>NSAdvertisingAttributionReportEndpoint</key> <string>https://linkrunner-skan.com</string> <key>AttributionCopyEndpoint</key> <string>https://linkrunner-skan.com</string> Apple automatically appends the .well-known paths: /.well-known/private-click-measurement/report-attribution/ /.well-known/skadnetwork/report-attribution/ ATS diagnostics for the domain: PASS for all tests (TLS 1.0–1.3, PFS disabled, arbitrary loads allowed, etc.) Both .well-known paths are publicly accessible and return 200 OK Testing Flow: Enabled Developer → AdAttributionKit Developer Mode on iOS (15+) Followed Apple’s official guide: Testing AdAttributionKit with Developer Mode Generated test impression using: createAdAttributionKitDevelopmentImpression implemented in SKANManager.swift Called Postback.updateConversionValue with lockPostback: true Created Development Postback from Developer Settings Waited 30+ minutes while intercepting server requests (proxy + backend logs) What We’ve Tried So Far: Confirmed ATS compliance with nscurl --ats-diagnostics (all PASS) Verified .well-known paths are accessible publicly without redirects Tested endpoints manually with a POST request – server responds 200 OK Confirmed Info.plist entries exactly match Apple’s required keys Double-checked iOS device is running iOS 15+ with Developer Mode enabled Repeated test flow multiple times with fresh impressions and postbacks Waited up to 1 hour for postback (in case of delays) Issue: No POST requests are being received from Apple to either .well-known endpoint, even though the setup appears correct and ATS tests pass. References Used: Configuring an Advertised App Generating JWS Impressions Question: Has anyone faced a similar issue where AdAttributionKit Development Postbacks are not firing despite correct Info.plist setup, ATS compliance, and reachable .well-known endpoints? Any insight into possible missing configuration steps or testing nuances would be greatly appreciated.
Replies
0
Boosts
1
Views
106
Activity
Aug ’25
Storekit configuration broken in Xcode 16.4 the file has been changed
Hi everyone, After updating to Xcode 16.4, my StoreKit configuration stopped working. Whenever I run the app with a .storekit file set as the active scheme, I immediately get this alert: “The file has been changed. Do you want to save your changes or revert to the file on disk?” No matter if I choose Save Anyway or Revert, StoreKit testing does not work - the purchases are not simulated, and the scheme is basically broken. This issue didn’t exist in Xcode 15.4 - the same StoreKit configuration file works fine there. What I tried so far: Clearing Derived Data - no effect Making sure no scripts/tools modify the .storekit file - still happens Restarting Xcode and macOS - no change Environment: Xcode 16.4 Happens in both Simulator and on device Reproducible 100% Has anyone else seen this in 16.4? Any known workarounds until Apple fixes it? Thanks!
Replies
5
Boosts
1
Views
489
Activity
Oct ’25
StoreKit 2 not loading subscription products
Hi everyone, I’m seeing a strange behavior with StoreKit 2 and I’d like to know if anyone else experienced this. My subscription group “ROTA Premium” (Monthly + Annual) is currently Waiting for Review in App Store Connect. What works In Xcode’s StoreKit sandbox, everything loads correctly: Products appear Trial starts Purchases work What doesn’t work In TestFlight and App Review, StoreKit 2 returns zero products, so my paywall shows: “No subscription options found.” There are: No geo restrictions No backend No VPN/IP filtering Paid Apps Agreement is accepted App Review said the device was online, but couldn’t give technical help. My question Has anyone seen StoreKit 2 fail to load subscription products when the subscription group is still in Waiting for Review? Do subscription groups need to be reviewed together with the app version for StoreKit 2 to return them in TestFlight/App Review? Any advice would be appreciated! Thanks.
Replies
0
Boosts
1
Views
92
Activity
Nov ’25
When is the unverified branch of AppTransaction.shared entered?
Hi all, I am adding the following StoreKit 2 code to my app, and I don't see anything in Apple's documentation that explains the unverified case. When is that case exercised? Is it when someone has tampered with the app receipt? Or is it for more mundane things like poor network connectivity? // Apple's docstring on `shared` states: // If your app fails to get an AppTransaction by accessing the shared property, see refresh(). // Source: https://developer.apple.com/documentation/storekit/apptransaction/shared var appTransaction: VerificationResult<AppTransaction>? do { appTransaction = try await AppTransaction.shared } catch { appTransaction = try? await AppTransaction.refresh() } guard let appTransaction = appTransaction else { AppLogger.error("Couldn't get the app store transaction") return false } switch appTransaction { case .unverified(appTransaction, verificationError): // For what reasons should I expect this branch to be entered in production? return await inspectAppTransaction(appTransaction, verifiedByApple: false) case .verified(let appTransaction): return await inspectAppTransaction(appTransaction, verifiedByApple: true) } Thank you, Lou
Replies
12
Boosts
1
Views
864
Activity
Apr ’25
App Store Localization is Rejected
Why is this rejection coming? I don't understand.I can't see anything in my mailbox.
Replies
6
Boosts
0
Views
2k
Activity
16h
StoreKit 2: Delayed Transaction and Entitlement Updates After Promo Code Subscription Redemption
I’m implementing a subscription purchase flow using promo code redemption via an external App Store URL. Flow: User taps “Purchase” in the app (spinner shown) App opens the promo redemption URL (apps.apple.com/redeem) User completes redemption in the App Store User returns to the app The app must determine whether the subscription was purchased within a reasonable time window The app listens to Transaction.updates and also checks Transaction.currentEntitlements when the app returns to the foreground. Issue: After redeeming a subscription promo code via the App Store and returning to the app, the app cannot reliably determine whether the subscription was successfully purchased within a short, user-acceptable time window. In many cases, neither Transaction.updates nor Transaction.currentEntitlements reflects the newly redeemed subscription immediately after returning to the app. The entitlement may appear only after a significant delay, or not within a 60-second timeout at all, even though the promo code redemption succeeded. Expected: When the user returns to the app after completing promo code redemption, StoreKit 2 should report the updated subscription entitlement shortly thereafter (e.g. within a few seconds) via either Transaction.updates or Transaction.currentEntitlements. Below is the minimal interactor used in the sample project. The app considers the purchase successful if either a verified transaction for the product is received via Transaction.updates, or the product appears in Transaction.currentEntitlements when the app returns to the foreground. Otherwise, the flow fails after a 60-second timeout. Questions: Is this entitlement propagation delay expected when redeeming promo codes through the App Store? Is there a recommended API or flow for immediately determining whether a subscription has been successfully redeemed? Is there a more reliable way to detect entitlement changes after promo code redemption without triggering user authentication prompts (e.g., from AppStore.sync())? import UIKit import StoreKit final class PromoPurchaseInteractor { private let timeout: TimeInterval = 60 private struct PendingOfferRedemption { let productId: String let completion: (Result<Bool, Error>) -> Void } private var pendingRedemption: PendingOfferRedemption? private var updatesTask: Task<Void, Never>? private var timeoutTask: Task<Void, Never>? enum DefaultError: Error { case generic case timeout } init() { NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil) } deinit { NotificationCenter.default.removeObserver(self) updatesTask?.cancel() timeoutTask?.cancel() } func purchaseProduct(using offerUrl: URL, productId: String, completion: @escaping (Result<Bool, Error>) -> Void) { guard pendingRedemption == nil else { completion(.failure(DefaultError.generic)) return } pendingRedemption = PendingOfferRedemption(productId: productId, completion: completion) startPurchase(using: offerUrl) } @objc private func willEnterForeground() { guard let pendingRedemption = pendingRedemption else { return } startTimeoutObserver() Task { if await hasEntitlement(for: pendingRedemption.productId) { await MainActor.run { self.completePurchase(result: .success(true)) } } } } private func startPurchase(using offerURL: URL) { startTransactionUpdatesObserver() UIApplication.shared.open(offerURL) { [weak self] success in guard let self = self else { return } if !success { self.completePurchase(result: .failure(DefaultError.generic)) } } } private func completePurchase(result: Result<Bool, Error>) { stopTransactionUpdatesObserver() stopTimeoutObserver() guard let _ = pendingRedemption else { return } pendingRedemption?.completion(result) pendingRedemption = nil } private func startTransactionUpdatesObserver() { updatesTask?.cancel() updatesTask = Task { for await update in Transaction.updates { guard case .verified(let transaction) = update else { continue } await MainActor.run { [weak self] in guard let self = self, let pending = self.pendingRedemption, transaction.productID == pending.productId else { return } self.completePurchase(result: .success(true)) } await transaction.finish() } } } private func stopTransactionUpdatesObserver() { updatesTask?.cancel() updatesTask = nil } private func startTimeoutObserver() { guard pendingRedemption != nil else { return } timeoutTask?.cancel() timeoutTask = Task { try? await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000)) await MainActor.run { [weak self] in self?.completePurchase(result: .failure(DefaultError.timeout)) } } } private func stopTimeoutObserver() { timeoutTask?.cancel() timeoutTask = nil } private func hasEntitlement(for productId: String) async -> Bool { for await result in Transaction.currentEntitlements { guard case .verified(let transaction) = result else { continue } if transaction.productID == productId { return true } } return false } }
Replies
1
Boosts
1
Views
206
Activity
Jan ’26
Promotional offer is not showing in In-app subscription sheet.
Hello, I have 2 subscription group. Each group have different plans and promotional offer. I was set 1 month free promotional offer in all plans. While I subscribe any plan IAP sheet is not showing 1 month free promotional offer. I already used with new sandbox account. How can I get promotional offer in IAP sheet?
Replies
4
Boosts
1
Views
933
Activity
Sep ’25
External Link Account API - ExternalLinkAccount.canOpen() always return false
This is a hybrid app built with JavaScript (Vue) + Capacitor. It is a reader app and has been authorized by Apple to use the External Link Account Entitlement, allowing users to manage their subscriptions outside of the app. I have implemented the External Link Account API. When I click on "Gerenciar Assinatura em...", I use the External Link Account API to check if the modal is available (using ExternalLinkAccount.canOpen()). I always get "false". my plugin in swift: my app: I believe this is due to the fact that I am in a development environment. My project is configured correctly in the following files: info.plist and App.entitlements. I also have the authorization in my profile visible in Xcode. I have attached screenshots for validation. The question is: should the External Link Account API work in a test environment? I am testing the build in Xcode with a physical iPhone with iOS 18. file info.plist: file App.entitlements: xcode with authorization in my profile: If you could let me know if I am doing something wrong, I would greatly appreciate it.
Replies
2
Boosts
0
Views
159
Activity
Jun ’25
StoreKit 2: Handle unfinished consumables
I have non-consumable and consumable in-app purchases in my app. The tutorial I was following stated Transaction.currentEntitlements includes unfinished consumables, which is incorrect according to the documentation. Is the correct way to handle unfinished consumables (and non-consumables) to implement Transaction.updates and call finish() if it’s verified? The documentation says that listener will receive unfinished transactions once upon app launch, so with that, do I understand correctly you do not need to implement Transaction.unfinished unless you want to look for unfinished transactions manually later on? Otherwise what is the correct and most recommended way to handle unfinished consumables? Is there a way to test that scenario in Xcode?
Replies
1
Boosts
0
Views
263
Activity
Jun ’25
Consolidate subscriptions between apps
Because of historical reasons we have the same app with different bundle identifiers in different App Stores, e. g. one in Germany and one in Poland and one in France. Every app offers a monthly and a yearly in app subscription. We want to consolidate our apps and user base and want to have only one app in the end. The question is now: Can Apple make the monthly/daily subscriptions from e. g. the app in the store in Poland available or migrate them to the german app in the german App Store when we make the "german" app available also in Poland? The final goal would be that a user who bought a subscription in the polish store would have the subscription also available on his Apple ID in the german store. Is this possible? Can Apple make this possible?
Replies
1
Boosts
0
Views
74
Activity
Jun ’25
Issue that subscription products (monthly subscription and annual subscription) cannot be obtained from App Store Connect
Dear my friends, I have set up two subscriptions (monthly and annual) in App Store Connect. By configuring with StoreKit, I can import the configurations from App Store Connect into the local .storekit file, and the program can run normally. However, when the StoreKit configuration is set to NONE, I am unable to retrieve product information from App Store Connect. The code for fetching the product list is as follows. Could you please provide suggestions on how to proceed? Thank you very much! private let productIds: [String] = ["subscription.year", "subscription.monthly"] subscriptions = try await Product.products(for: productIds)
Replies
2
Boosts
0
Views
138
Activity
Jun ’25
In-App Purchase products suddenly not returned by queryProductDetails
Hi, We're currently experiencing an issue with consumable In-App Purchases on our production iOS app. Until the end of May, everything was working as expected, but starting in early June, our app no longer receives any products when calling queryProductDetails() using Flutter’s in_app_purchase plugin (which utilizes StoreKit). Here’s what we’ve confirmed so far: The product IDs are correctly configured in App Store Connect, and all items are marked as “Approved.” No recent changes have been made to the bundle ID or the product IDs. The “Base Territory” setting was updated for each IAP item in early May. After that change, product retrieval and purchases were working normally through the end of May. This issue is happening on real devices in production, and multiple users are affected. The same functionality continues to work correctly on Android. All requested product IDs are being returned in the notFoundIDs list of the queryProductDetails() response. We're quite puzzled by this issue as no clear cause has been identified so far. Any thoughts on this issue would be much appreciated. Thank you!
Replies
1
Boosts
0
Views
179
Activity
Jun ’25
Why Non-Consumable product has originalTransactionId?
I try to call Get Transaction Info from App Store Server API, and the transactionId is for a Non-consumable type product, but it is odd that there are so many different transactionId and they have a same originalTransactionId { "bundleId": "${bundleId}", "environment": "Production", "inAppOwnershipType": "PURCHASED", "originalPurchaseDate": 1691220528000, "originalTransactionId": "${originalTransactionId}", "productId": "${productId}", "purchaseDate": 1691220528000, "quantity": 1, "signedDate": 1692590989925, "storefront": "USA", "storefrontId": "143441", "transactionId": "${originalTransactionId}", "transactionReason": "PURCHASE", "type": "Non-Consumable" } the defination of Non-Consumable is can only purchase once for same apple account. But why there would have originalTransactionId?
Replies
3
Boosts
0
Views
1.2k
Activity
May ’25