Both the legacy StoreKit API and the new StoreKit 2 API return the incorrect storefront countryCode. My actual Apple ID region is Germany, and my Sandbox test user is set to France, yet the SDK consistently returns USA.
Expected Results:
The returned storefront countryCode should reflect the correct region - sandbox user region if signed in and real user region if not signed in with sandbox).
Actual Results:
Returned country code is USA with both
SKPaymentQueue.default().storefront?.countryCode
and
await Storefront.current?.countryCode.
Signing out/in, device reboot and even reset do not help, I'm stuck with USA storefront.
StoreKit
RSS for tagSupport in-app purchases and interactions with the App Store using StoreKit.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Hello,
Our app is approved for the Advanced Commerce API and we are currently testing in the Sandbox environment only.
We have created generic product identifiers and have already submitted them via the Advanced Commerce API Access form.
However, the generic product status in App Store Connect is still “Ready to Submit.”
For Sandbox testing, is this status expected, or do we need to submit an app build or the generic product for review before Advanced Commerce works correctly?
Thank you.
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
Subscriptions
In-App Purchase
Advanced Commerce API
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
}
}
I had published an App, and my app has App Clip supported. The issue I faced is that I had received complaints where the user keep seeing the pop up "Apple Media Services Terms and Conditions Have Changed" when user clicked on the "Open" Button in the App Clip.
What we had tried so far:
Let user switch the Apple Id's region to our supported region.
Let user try to log out and log in to Apple Id within the supported region.
I've been trying to make my app available on the App Store for a month now, but I can't because the signatures I created don't appear in the sandbox app. I did all the configuration in the store and in the app. I tested the same code in another app with signatures and it was loaded, but the signature for that specific app doesn't appear. I've tried contacting Apple support, but they can't help me. It almost seems like it's on purpose. I'm treated like crap and they don't even give me an explanation about what's happening.
Can anyone help me?
Hi,
I'm using the AppTransaction.shared API to retrieve the originalAppVersion, but I'm encountering issues for users who originally installed the app in 2017. Specifically, the property doesn't seem to return the expected value (or returns nil) for these older accounts.
I have verified this issue using a real App Store purchase from 2017.
Steps to Reproduce:
Use a test account that originally downloaded the app in 2017.
Call:
let shared = try await AppTransaction.shared
print(shared.originalAppVersion)
Observe that originalAppVersion is missing or not returned correctly.
Any insights on whether this is expected behavior for very old App Store purchases, or if there is a workaround to reliably detect the original app version?
Thanks in advance!
I'm working on a watchOS app that has an iOS counterpart. There will be a subscription required to unlock functionality and I would like the user to be able to make the purchase on either the iPhone or the watch and have both apps unlock.
The first link below says that StoreKit 2's Transaction.currentEntitlements will not work in this case like it does with extensions. The second link says it might work but doesn't in the sandbox.
What is the best way to make this work? Will it just work in the App Store? Should I use WCSession to send the purchase information from one platform to the other and store it in the keychain? Something else?
Via https://www.revenuecat.com/blog/engineering/ios-in-app-subscription-tutorial-with-storekit-2-and-swift/
"Transaction.currentEntitlements can be used in extensions the same way it was used in the previous steps. This works for extensions like Widgets and Intents. However, an iOS app with a companion watchOS app will not work even though Transaction.currentEntitlements can be executed in it. A companion watch app does not stay updated with the same transaction history as its iOS app because they are separate platforms."
Via https://developer.apple.com/forums/thread/739963
"In TestFlight I was able to confirm that the Watch app and IOS app share in-app purchases. It seems the problems confirming this with Storekit and Sandbox are limits of the testing environments."
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?
Hello,
In my iOS app, I have a customer center where the user can see some details about its current subscription. I display things like the billing period, the price, the introductory offer state, the renewal date if it's not cancelled or the expiration date if it's cancelled, etc. From this screen, the user can open the subscription management sheet.
I want to detect if the user cancels the subscription from this sheet or from the App Store (when the app is running) so I can refresh the information displayed on my customer center.
I checked the asynchronous sequences provided by StoreKit 2 like Transaction.updates or Product.SubscriptionInfo.Status.updates and tested with a Sandbox account on my physical device with the app debugged using Xcode. But I noticed these sequences don't emit when I cancel the subscription in Sandbox.
Is this the expected behavior?
Is there a way to observe in real time if a user cancels the subscription?
I can still manually check when the sheet is dismissed but it's not ideal because I want to know even if the user cancel from outside of the app with the app running.
Thank you,
Axel
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
Subscriptions
StoreKit Test
StoreKit
In-App Purchase
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!
Hi, I'm reaching out to report a recurring issue with in-app purchases on iOS that seems to be related to Apple’s transaction handling — not to third-party libraries.
In my Flutter application, I use both StoreKit2 and StoreKit1 (for comparison) via different packages, including the official in_app_purchase package. However, in both cases, I’m experiencing unexpected reuse of transactionId and appTransactionId values, even when initiating fresh purchases with unique appAccountToken values.
Problem Summary:
Purchase Stream Returns Old Purchases
When calling buyNonConsumable() with a new product, the purchase stream still returns data for a previously purchased product, despite clearing all Sandbox transactions and using a new applicationUserName for each attempt.
Transaction IDs Reused Across Distinct Purchases
Even when generating a new UUID for appAccountToken on each purchase, the returned appTransactionId and transactionId are reused — this breaks our server-side logic, which expects these fields to uniquely identify purchases and users.
Example Logs:
// First purchase
{
"appAccountToken": "2d5a0880-f68e-44a7-a414-f51204e63904",
"appTransactionId": "704464472748013865",
"transactionId": "2000000928154716"
}
// Second purchase (different user context)
{
"appAccountToken": "2d5a0880-f68e-44a7-a414-f51204e63904",
"appTransactionId": "704464472748013865",
"transactionId": "2000000928429780"
}
Even when using a different productId, the appTransactionId stays the same. When using StoreKit1, the productId updates properly, but the transactionId still matches the previous one.
This behavior also affects App Store Server Notifications (V2): we have observed notifications tied to appAccountTokens from completely different user accounts (based on internal logs), sometimes delayed by days or weeks.
I’ve prepared a reproducible example using the official Flutter in_app_purchase sample with minimal changes — you can find it here:
Github gist
The code is almost identical to the package example. I only added UUID generation for applicationUserName in _getToken(). In the actual app (not in this example), I retrieve the token from an API.
Additional Observations from the Community:
We’ve also found similar issues reported in other frameworks and languages. For instance, a developer using react-native-iap observed that App Store Server Notifications in TestFlight were tied to previously deleted users, even after signing up with a new user account and generating a new appAccountToken. Details here:
User A deleted → User B signs up → receives upgrade event with User A’s token
Notification uses appAccountToken from old account, not the new one
This strengthens the suspicion that the issue may be related to how Apple associates transactions with Apple IDs in test environments.
Questions:
Is it expected for transactionId or appTransactionId to persist across purchases within the same Apple ID, even for different user contexts (e.g., separate logins in the same app)?
Is there any official recommendation for avoiding this kind of data reuse in Sandbox or TestFlight environments?
Should I expect appAccountToken in server notifications to always match the latest value provided during the purchase?
Thank you in advance for your assistance. I would appreciate any clarification or advice regarding this issue, as it impacts production logic that relies on these identifiers being unique and consistent.
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
Subscriptions
StoreKit Test
StoreKit
In-App Purchase
在iOS 26.4 beta3版本中,当前支付完成,获取的票据是上次支付的票据,在拉起一笔支付成,获取的又是上次支付的票据
Topic:
App & System Services
SubTopic:
StoreKit
Yesterday I noticed that if I purchase a free trial subscription in my app using a sandbox account, when the subscription expires I see a “Billing Problem” message every time I open the app.
”allow purchases & renewals“ setting is ON so this shouldn’t happen.
has anyone else seen this or knows how to resolve?
observed on iOS 18.3.2 & 16.7.10
thanks
We're experiencing an issue with in-app purchases in our React Native iOS app where RNIap.getProducts() is consistently returning an empty array, preventing users from making purchases.
Technical Details:
Using react-native-iap library
Product ID: '[REDACTED]'
Platform: iOS
Product Type: Consumable (one-time payment, NOT subscription)
Error in logs: "No products returned from App Store. Check App Store Connect."
Debugging shows: Products Fetched: []
Steps to Reproduce:
Open app and navigate to the quiz feature
Attempt to purchase additional quiz attempts
Modal opens but fails to load product information
What We've Confirmed:
IAP Connection initializes successfully
No error is thrown during product fetch, just an empty array returned
App is configured with correct Bundle ID
Using Apple Sandbox test account
We are implementing consumable purchases (one-time payments)
Potential Issues to Investigate:
Product configuration in App Store Connect
Ensure product is set as "Consumable" type in App Store Connect
Sandbox tester account permissions/activation
Bundle ID matching between app and App Store Connect
Product ID case sensitivity or typos
I am using the public url https://api.storekit-sandbox.itunes.apple.com/inApps/v1/notifications/jwsPublicKeys to get the jwks keys to verify the signed payload for store kit payments. I am checking Apple server notifications.
const APPLE_JWKS_URL = "https://api.storekit-sandbox.itunes.apple.com/inApps/v1/notifications/jwsPublicKeys"
// Apple JWK set (cached by jose)
const appleJWKS = createRemoteJWKSet(new URL(APPLE_JWKS_URL));
const jwks = await appleJWKS();
logger.debug("Apple JWKS Keys: %O", jwks); // Log the keys
if (!signedPayload) {
// return res.status(400).json({ error: "Missing signedPayload" });
}
// Step 1: Verify JWS (signature + payload) using Apple's JWKS
const { payload, protectedHeader } = await jwtVerify(
signedPayload,
appleJWKS,
{
algorithms: ["ES256"], // Apple uses ES256 for signing
}
);
I am testing sandbox subscribe but I always recieve DID_FAIL_TO_RENEW
I try to create new sandbox account and clear purchase history but not work
can anyone help?
Testing recurring subscriptions in a new release in both development environment and in Test Flight, we see an increased percentage of failed calls returning from Apple server. The return failure message is that an unknown error occurred.
The behavior is not consistent and most often we get a failure and another attempt succeeds. We do get around 50% failures which is alarming and cannot be explained to the end user.
We have also experimented with previous test versions that are in production currently and we see the same behavior, so this is not isolated to the most recent release we plan nor to a specific application we have. Past testing in previous releases did not return such high percentage of failures from Apple server.
Have you encountered such an issue? We have no idea what may cause Apple server to return with this "Unknown error" and there is no log we can consume from Apple subscription service.
Appreciate your help on this.
New subscriptions have been failing to renew in the sandbox for 3 days. I am seeing multiple posts and comments from people that appear to be experiencing the same issue. But I haven't seen any feedback from Apple representatives.
I really do not want to launch a new app without seeing functioning renewals in the sandbox.
Is there somewhere else we are intended to seek assistance?
The app review prompt on iOS/iPadOS 26.1 has the "Not Now" button greyed out:
On iOS/iPadOS 26.0, this was working:
Before filing a radar, is there anything I'm missing? The only way to dismiss it is to tap a star rating and then a Cancel button appears which lets you dismiss it without reviewing.
Topic:
App & System Services
SubTopic:
StoreKit
💬 Post Content
Hello everyone,
I’m currently testing In-App Purchases (auto-renewable subscriptions) for my iOS app,
and I’m experiencing an issue where the product information cannot be fetched from StoreKit.
❓ Questions
For sandbox testing, is it absolutely necessary to submit a new app version for review (with the in-app purchase included)? Or can I test subscriptions without submitting a new build to App Review?
Under what conditions does the error
IAPError(code: storekit_no_response,
source: app_store,
message: StoreKit: Failed to get response from platform.)
🧪 What I’ve Tried
• Confirmed that the bundle identifier matches the App Store Connect record
• Verified that In-App Purchase capability is enabled in Xcode
• Ensured the sandbox tester account is logged in (Settings → App Store)
• Removed the StoreKit configuration file to use the sandbox environment
• Tried both real device and simulator (same error)
• Accepted the Paid Apps Agreement in App Store Connect
• Products are created but currently not yet submitted for review
🔍 Environment
• App type: iOS app built with Flutter (using in_app_purchase plugin)
• Build type: Archive build installed via Xcode (Run on device) and testFlight
• StoreKit Configuration File: Currently removed (for sandbox test);
• Status: Ready to submit in App Store Connect
Topic:
App & System Services
SubTopic:
StoreKit