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

Subscription Renewals in TestFlight Sandbox Testing
What happens after 12 renewals? Does the subscription expire completely? The next day when I try to manage settings for my sandbox account it says it cannot connect. I cannot see the status of subscription in: Settings-> App Store -> Sandbox Account -> Manage Logging out and logging in of my regular account does not fix that. When I login with a different non-testflight sandbox account I can finally edit those settings. There are some missing details in documentation explaining testflight sandbox accounts. Do these accounts stop working after 12 auto-renewals? I need more specific details in order to ensure subscriptions will work properly during production.
1
1
501
Jan ’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
349
Oct ’25
Unexpected Change in Apple Refund Handling CONSUMPTION_REQUEST - Impact on Subscription App with AI Backend
We offer a 3-day free trial, and our paywall clearly states that users will be charged after the trial ends. However, some users request refunds after the charge - even after fully using our app for days or even weeks. In some cases, refunds are approved despite the users having consumed our AI processing services for up to a month. Since our app relies on backend AI processing, each user session incurs a real cost. To prevent losses, we utilize RevenueCat’s CONSUMPTION_REQUEST system and have set our refundPreference to: "2. You prefer that Apple declines the refund". Until recently, Apple typically respected this preference, and 90% of refund requests were declined as intended. However, starting about a week ago, we observed a sudden reversal: Apple is now approving around 90% of refund requests, despite our refund preference. As a result, we are operating at a loss and have had to halt both our marketing campaigns and our 3-day free trial. We’re trying to understand whether this shift is due to a change in Apple’s refund policy, or if we need to handle CONSUMPTION_REQUEST differently on our end. Has anyone else experienced similar changes? Any insights would be greatly appreciated.
0
1
286
May ’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
38
Aug ’25
SKTestSession.setSimulatedError() not working for .loadProducts API in iOS 26.2
Description SKTestSession.setSimulatedError() does not throw the configured error when testing StoreKit with the .loadProducts API in iOS 26.2. The simulated error is ignored, and products load successfully instead. Environment iOS: 26.2 (Simulator) Xcode: 26.2 beta 2 (Build 17C5038g) macOS: 15.6.1 Framework: StoreKitTest Testing Framework: Swift Testing base project: https://developer.apple.com/documentation/StoreKit/implementing-a-store-in-your-app-using-the-storekit-api Expected Behavior After calling session.setSimulatedError(.generic(.notAvailableInStorefront), forAPI: .loadProducts), the subsequent call to Product.products(for:) should throw StoreKitError.notAvailableInStorefront. Actual Behavior The error is not thrown. Products load successfully as if setSimulatedError() was never called. Steps to Reproduce Create an SKTestSession with a StoreKit configuration file Call session.setSimulatedError(.generic(.notAvailableInStorefront), forAPI: .loadProducts) Call Product.products(for:) with a valid product ID Observe that no error is thrown and the product loads successfully Sample Code import StoreKitTest import Testing struct SKDemoTests { let productID: String = "plus.standard" @Test func testSimulatedErrorForLoadProducts() async throws { let storeKitConfigURL = try Self.getStoreKitConfigurationURL() let session = try SKTestSession(contentsOf: storeKitConfigURL) try await session.setSimulatedError( .generic(.notAvailableInStorefront), forAPI: .loadProducts ) try await confirmation("StoreKitError throw") { throwStoreKitError in do { _ = try await Self.execute(productID: productID) } catch let error as StoreKitError { guard case StoreKitError.notAvailableInStorefront = error else { throw error } throwStoreKitError() } catch { Issue.record( "Expect StoreKitError. Error: \(error.localizedDescription)" ) } } #expect(session.allTransactions().isEmpty) } static func execute(productID: String) async throws { guard let product = try await Product.products(for: [productID]).first( where: { $0.id == productID }) else { throw NSError( domain: "SKDemoTests", code: 404, userInfo: [NSLocalizedDescriptionKey: "Product not found for ID: \(productID)"] ) } _ = product } static func getStoreKitConfigurationURL() throws -> URL { guard let bundle = Bundle(identifier: "your.bundle.identifier"), let url = bundle.url(forResource: "Products", withExtension: "storekit") else { fatalError("StoreKit configuration not found") } return url } } Test Result The test fails because the expected StoreKitError.notAvailableInStorefront is never thrown. Question Is this a known issue in iOS 26.2 / Xcode 26.2 beta 2, or is there a different approach required for simulating errors with SKTestSession in this version? Any guidance would be appreciated. Feedback Assistant report: FB21110809
0
1
16
5d
Unknown Error when Trying to Add New Offer Codes
Hi there! Whenever I try to add a new Offer Code for my app's subscription, I get an unknown error. When I get to the last step of the "Create Offer for Codes" flow, I get an error that error reads "An error has occurred. Try again later." I have been getting this same error for over a week now, so any help figuring out how to add new offer codes would be greatly appreciated!
2
1
560
3w
SubscriptionStoreView - Restoring Subscriptions
I'm using code similar to the following to conditionally show the SubscriptionStoreView and the .storeButton(.visible, for: .restorePurchases) modifier is used to allow the user to restore an existing subscription. How can I listen for events that would allow me to close this view once the subscription is restored? The .onInAppPurchaseCompletion closure does not handle this and it also appears that listening for results in Transaction.currentEntitlements also doesn't handle the fact that a subscription is restored. Any guidance on how to determine if the subscription has been restored would be greatly appreciated. Finally, how can this be tested effectively in both TestFlight and in Xcode with the simulator. if subscriptionManager.subscription == .none { SubscriptionStoreView(groupID: "1234567") { SubscriptionMarketingView(transparency: false) .containerBackground(for: .subscriptionStoreFullHeight) { GradientBackground() } } .backgroundStyle(.clear) .storeButton(.visible, for: .restorePurchases) .storeButton(.visible, for: .redeemCode) .onInAppPurchaseCompletion { product, result in Task { await subscriptionManager.entitlements() } } }
0
1
71
1w
storefront.countryCode is fixed to USA in iOS 26 beta
Whether using Storefront.current?.countryCode or SKPaymentQueue.default().storefront?.countryCode, both are returning "USA" only. (It used to return the correct country code before the update.) In the sandbox environment, the country code is returned correctly, but in the TestFlight environment, it always returns "USA". There's no mention of this behavior in the beta release notes, so I'm posting it here for visibility.
1
1
114
Jul ’25
StoreKit sandbox purchase and product fetch not working (IAPError: storekit_no_response)
💬 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
0
1
79
2w
Inquiry Regarding Potential StoreKit v2 Transaction Handling Issue
Dear Apple Technical Support Team, We have encountered a potential issue related to transaction handling while using StoreKit v2, and would greatly appreciate your assistance in confirming the behavior or providing any relevant guidance. Issue Description: When calling Transaction.unfinished and listening to Transaction.updates on the client side, we noticed that some transactions—despite having already been processed and successfully completed with finish()—are being returned again upon the next app launch, which results in duplicate receipt uploads. Current Handling Flow: 1. Upon app launch: • Iterate over Transaction.unfinished to retrieve unfinished transactions; • Simultaneously listen for transaction changes via Transaction.updates (e.g., renewals, refunds); 2. For each verified transaction, we immediately call await transaction.finish(); 3. We then construct a transaction model, store it locally, and report it to our backend for receipt verification; 4. After the server successfully verifies the receipt, the client deletes the corresponding local record; 5. On every app launch, the client checks for any locally stored receipts that haven’t been uploaded, and re-uploads them if necessary. Key Code Snippets: private static func verifyReceipt(receiptResult: VerificationResult) -> Transaction? { switch receiptResult { case .unverified(_, _): return nil case .verified(let signedType): return signedType } } public static func handleUnfinishedTransactions(payConfig: YCStoreKitPayConfig, complete: ((YCStoreKitReceiptModel?) -> Void)?) { Task.detached { for await unfinishedResult in Transaction.unfinished { let transaction = YCStoreKitV2Manager.verifyReceipt(receiptResult: unfinishedResult) if let transaction { await transaction.finish() if transaction.revocationDate == nil { let receipt = YCStoreKitV2Manager.createStoreKitReceiptModel( transation: transaction, jwsString: unfinishedResult.jwsRepresentation, payConfig: payConfig, isRenew: false ) complete?(receipt) } } } } } private func observeTransactionUpdates() -> Task<Void, Never> { return Task { for await updateResult in Transaction.updates { let transaction = YCStoreKitV2Manager.verifyReceipt(receiptResult: updateResult) if let transaction { await transaction.finish() if transaction.revocationDate == nil { let receipt = YCStoreKitV2Manager.createStoreKitReceiptModel( transation: transaction, jwsString: updateResult.jwsRepresentation, payConfig: self.payConfig, isRenew: false ) self.callProgressChanged(.receiptPrepared, receiptModel: receipt, errorType: .none, error: nil) } } } } } Our Questions: 1. Is it possible for Transaction.unfinished or Transaction.updates to return transactions that have already been finished? Specifically, if a transaction was successfully finished in a previous app launch, could it still be returned again during the next launch? 2. Are there any flaws in our current handling process? Our current sequence is: finish() → construct model → local save → report to server → delete after verification. Could this order lead to timing issues where StoreKit considers a transaction unfinished? 3. If we need your assistance in investigating specific user transaction records or logs, what key information should we provide? We greatly appreciate your support and look forward to your response to help us further optimize our transaction processing logic.
1
0
83
Jul ’25
SKProductsRequest Randomly Returns All Product IDs as Invalid Despite Correct IDs
We're experiencing an unusual issue with SKProductsRequest in our app's in-app purchases. Despite confirming that all our product IDs are correct, the invalidProductIdentifiers property of SKProductsResponse sometimes contains all the requested product IDs. Here are the specifics: The issue occurs randomly and is not persistent. For instance, around 2024-12-26 -8.0 01:06:04, this problem occurred 3 times in quick succession. The didFailWithError method of SKRequestDelegate is not triggered, reporting no errors. Some users report that after encountering this issue, it resolves itself after about an hour. However, other users have reported this problem persisting for several days. We have repeatedly verified the correctness of our product IDs. Given the intermittent nature of the problem, we suspect this might be an issue on the App Store server side. We're looking for suggestions to resolve or mitigate this issue to improve the reliability of in-app purchases in our application. Has anyone encountered a similar issue? Or does anyone have suggestions that could help us further diagnose and resolve this problem?
0
1
339
Dec ’24
Unexpected 401 Unauthorized response from production endpoint when using sandbox transactionId with Get Transaction Info API
We have encountered an issue when verifying transactions using the Get Transaction Info API. We tested the behavior in both the sandbox and production environments and observed the following results. When calling the production endpoint: https://api.storekit.itunes.apple.com/inApps/v1/transactions/{transactionId} with a transactionId generated in the sandbox environment, the API returns HTTP 401 Unauthorized. However, based on the documentation and common understanding, we expected HTTP 404 Not Found in this case. Using the same JWT token, if we call the sandbox endpoint: https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/{transactionId}, we receive HTTP 200 OK with the expected response body. We have also confirmed that the same behavior occurs when using the Get Transaction History API — it works correctly in the sandbox environment but returns 401 in production. Could you please confirm whether this behavior (receiving 401 instead of 404) is expected by design, or if it indicates a potential issue? If this is not the intended behavior, we would appreciate any guidance or instructions to resolve it. Thank you very much for your technical support. 「Get Transaction Info」APIを用いてトランザクションの検証を行ったところ、以下の問題が発生しました。 サンドボックス環境および本番環境の両方で検証を行い、次の結果を確認しています。 本番環境エンドポイント https://api.storekit.itunes.apple.com/inApps/v1/transactions/{transactionId} に対して サンドボックス環境で生成された transactionId を使用すると、HTTP 401 Unauthorized が返却されます。 (一般的には、この場合 404 Not Found が返る想定であると理解しています。) 同一のJWTトークン を用いて サンドボックス環境のエンドポイント https://api.storekit-sandbox.itunes.apple.com/inApps/v1/transactions/{transactionId} を呼び出した場合は、HTTP 200 OK が返り、期待通りのレスポンスボディを受け取ることができています。 また、同様の挙動が Get Transaction History を使用した場合にも発生することを確認しています。 サンドボックス環境では正常に動作しますが、本番環境では401が返却されます。 この挙動(401が返却されること)は仕様上想定されたものか、または何らかの問題によるものかご確認をお願いいたします。 もし想定外の挙動である場合は、解決に向けたご案内をいただけますと幸いです。 本件について、技術的なサポートをお願いいたします。 よろしくお願いいたします。
2
0
181
2w
Best way to share subscriptions between iOS and watchOS apps
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."
0
1
282
Mar ’25
Unable to retrieve data from In App Purchase
I want to add in-app purchasing to my app, but I can't figure out what part of my workflow is wrong. I created a product for my app in iTunes Connect (the product ID is com.mycompany.products.***) and it's in "Ready to submit" status. I created a sandbox test user for this app. I connected to iTunes on a real device using the sandbox AppleID. I went back to XCode and added in-app purchasing to my app. I turned on developer mode on the real device and logged in as the sandbox user. I built the app and ran it on a real device (not the simulator). I tried to get product information (com.mycompany.products.***) but nothing was returned. In-app purchasing is registered in App Store Connect and the status is "Ready to submit". The code only retrieves product information in a simple way, so I don't think there's a problem. inAppPurchase.getProducts(["com.mycompany.products.***"]).then(console.log).catch(console.error); But it only returns an empty array. What could be wrong? Any help would be much appreciated.
2
1
65
Jul ’25
Mismatch between App Store Server API `expiresDate` (July 23) and iOS UI “Expires on” date (July 22) for 1-month subscription
Hi everyone, I’m seeing a consistent one-day discrepancy between the expiresDate returned by the App Store Server API and the “Expires on” date shown in the iOS Settings / App Store subscription list. I’d like to confirm whether this behavior is expected or if I’m misunderstanding the way Apple rounds dates. Reproduction steps Step Action Result 1 Purchase a 1-month auto-renewable subscription on 23 June 2025 14:00 JST (UTC+9) Transaction succeeds 2 Immediately fetch the transaction with GET /inApps/v1/subscriptions/{transactionId} Response contains "expiresDate": "2025-07-23T05:00:00Z" (= 23 July 2025 14:00 JST) 3 On the same device open Settings › Apple ID › Subscriptions (or App Store › Account › Subscriptions) UI shows Expires on: 22 July 2025 The same happens for every monthly renewal and on multiple devices. Region is Japan, device time zone Asia/Tokyo. What I understand so far (and my hypothesis) Apple’s docs say a monthly subscription renews “on the same calendar date” of the next month, so renewal in this example is 23 July. If the renewal is scheduled for 23 July at 14:00 JST, the subscription is fully usable until the end of 22 July in calendar terms, because the new billing period starts the moment the 23rd begins in Apple’s canonical time zone. Therefore, it might be intentional for the UI to display 22 July—i.e., “you can keep using it through the 22nd; on the 23rd it renews.” This hypothesis makes sense internally, yet it still looks confusing to end users who read “Expires on 22 July” and assume access ends at 00:00 on the 22nd, a whole day earlier than in reality. Questions Is showing the day before the renewal date the official/expected behavior? If so, could Apple clarify that the “Expires on” label represents the last full calendar day rather than the exact expiry timestamp? Which value should we surface in-app when telling users “Your subscription is valid until …”? The server’s expiresDate (precise to the second, converted to user time zone), or A UI-style date that’s one day earlier, matching Settings / App Store? Does Apple have a public document describing this rounding/visual convention? Have other developers encountered user confusion about the apparent 1-day “shortening” and, if so, how did you word your in-app messaging? Any insight from Apple engineers or fellow developers would be greatly appreciated. Thank you!
0
1
229
Jun ’25
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
168
5d
restoreCompletedTransactions not working on iOS 26
I use [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]. Works on my App which is in the store (compiled pre-iOS 26). If I compile the same App now, same codebase with Xcode Version 26.0, restore does not work. Nothing happens. Tested on real device (iOS 26). Documentation says its deprecated, but my deployment target is iOS 12. Anyone has similar issues? Any recommendations?
2
0
79
Sep ’25
APIs available in Objective-C equivalent to Storekit2
The following document states that some APIs in Storekit1 have been deprecated and that migration to Storekit2 is recommended, but I understand that Storekit2 only supports Swift. I'm currently creating an iPhone app in Objective-C, but is there a way to implement APIs equivalent to Storekit2 using a different Objective-C API? https://developer.apple.com/documentation/ios-ipados-release-notes/ios-ipados-18-release-notes#StoreKit
1
1
454
Nov ’24
Incorrect storefront country code
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.
3
1
223
Oct ’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
181
Oct ’25