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

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
129
20h
Apple Media Services T&C pop-up
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.
0
0
21
1d
Advanced Commerce (Sandbox) – Generic Product Still “Ready to Submit”
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.
0
0
59
1d
IAP not available on iOS but on Mac Catalyst (Invalid IAP ID)
After adding furhter IAP Items to my app, none of the products are available for purchase an more on iOS. But it works just fine on the Mac Catalyst app. Logging the request shows that all product IAP IDs are "invalid", even those who already were on sale. Any idea what this can be caused by? Ive already double checked the obvious things like the product IDs on appstoreconnect, bundle ID, tested on different devices, Test Flight etc... Has anyone experienced this already?
0
0
62
3d
HELP WITH SUBSCRIPTIONS
Hey everyone, I really need help. My app versions keep getting approved for distribution and my subscriptions and business agreements are all approved. Yet, when the paywall in my app appears, and someone clicks the subscribe button to pay, the IAP isn't appearing. It just loads forever. When I tested in Xcode it just kept saying products not found. Id's are the same, bundle id is the same, ive done everything. Can someone help pls.
2
0
106
5d
Billing Problem while subscription renewal.
Hello, I'm currently experiencing issues with IAP subscription setup. The following error appears: "Billing Problem, There was a problem with your subscription renewal. To resolve, turn on Allow Purchases & Renewals, or leave off to test failed in-app purchase attempts and subscription renewals." I'm testing with a sandbox account, and automatic subscription renewal is turned on in the sandbox settings. A notification screen appears at the OS level, and consequently, a DID_FAIL_TO_RENEW error occurs on our payment server. I cannot determine the cause at all, so I would appreciate your assistance in checking this issue.
21
15
1.4k
6d
Purchase Intent does not work when app has been launched
I'm implementing PurchaseIntent.intents for App Store in-app purchase promotions, following Apple's WWDC guidance. The API only works on cold launch (killed→launch), but fails on background→foreground transitions, making App Store promotions unusable. Sample code as followed from WWDC23 video "What's new in StoreKit 2 and StoreKit Testing in Xcode". In the StoreKitManager observable class, I have this function which is initialized in a listening task: func listenForPurchaseIntent() -> Task<Void, Error> { return Task { [weak self] in for await purchase in PurchaseIntent.intents { guard let self else { continue } let product = purchase.product await self.purchaseProduct(product) } } } where purchaseProduct() will perform the call to: try await product.purchase() ISSUE: When the app is in background (after previously launched), and the purchase intent is initiated from Xcode Transaction Manager or using the "itms-services://?action=purchaseIntent" method, the system foregrounds my app but the purchase intent is never delivered to the waiting listener. The intent remains queued until the next cold launch (quit app and relaunch app). This could mean that if a user has installed the app, and has run the app, then tapped the promotional IAP from the App Store, the purchase intent will not show up until the next cold launch. If the app is in quit state, then the system will foreground the app, and purchase intent is delivered correctly. STEPS TO REPRODUCE Launch app (listener starts in StoreKitManager.init()) Background app Add purchase intent via Xcode Transaction Manager Foreground app Result: No purchase sheet appears, no intent delivered Workaround attempts: Using this either in a view or the main app: func checkForPurchaseIntents() async { for await purchaseIntent in PurchaseIntent.intents { await storeKit.purchaseProduct(purchaseIntent.product) } } Applied to .onChange(of: scenePhase) - Doesn't work, nothing happens. Using UIApplication.willEnterForegroundNotification - Only works on the first time the app goes from background to foreground when purchase intent is sent. Doesn't work on second time or third time. • Attempting to creating fresh listening task on each foreground - Does not work. The question is: How are we supposed to implement the PurchaseIntent API? I have checked Apple sample projects like BackyardBirds, and sample projects from WWDC on StoreKit 2 but they never implemented Purchase Intent.
0
1
62
6d
ExternalPurchaseCustomLink.token(for:) returns nil when isEligible is true
Environment iOS 18.1+ StoreKit External Purchase Link Entitlement (EU) App distributed via App Store in France Problem Summary I'm implementing external purchase links for EU users using ExternalPurchaseCustomLink. While the implementation works correctly in my TestFlight testing, some production users experience token(for:) returning nil. Implementation Following Apple's documentation, my flow is: Check eligibility using ExternalPurchaseCustomLink.isEligible If eligible, call ExternalPurchaseCustomLink.token(for: "ACQUISITION") Store the token for use in the external purchase flow // Simplified implementation guard #available(iOS 18.1, *) else { return } let isEligible = await ExternalPurchaseCustomLink.isEligible guard isEligible else { return } // This returns nil for some users despite isEligible being true let token = try await ExternalPurchaseCustomLink.token(for: "ACQUISITION") Configuration Entitlement: com.apple.developer.storekit.external-purchase-link is present Info.plist: SKExternalPurchaseCustomLinkRegions set to ["fr"] App is only available in France via App Store Observed Behavior For affected users: ExternalPurchaseCustomLink.isEligible returns true token(for:) returns nil (not throwing an error) The token generation was never previously called for these users Questions Under what conditions does token(for:) return nil when isEligible is true? Is there additional validation I should perform before calling token(for:)? Are there known issues with token generation on specific iOS versions? Any guidance on debugging this issue would be appreciated.
1
0
81
1w
StoreKit 2 returns empty products array in TestFlight
I'm using StoreKit 2 with Product.products(for:) to fetch my auto-renewable subscriptions. It works in the Xcode simulator with a local StoreKit configuration file, but returns an empty array (no error) in TestFlight. iOS 15+, using async/await API Products are configured in App Store Connect Paid Apps agreement is active Sandbox tester account set up Has anyone experienced this? What am I missing?
0
0
34
1w
SubscriptionStoreView(groupID:) bug in TestFlight
Hello, I seem to have a strange bug when testing 2 of my Apps that calls SubscriptionStoreView(groupID: storekitmanager.groupID), where storekitmanager is using @Observable, with groupID from the Subscription Group in App Store Connect. They have the following: Yearly, Biannually, and Monthly. Their productIDs and groupIDs have been configured in each app with the correct IDs. In my main Apple ID, when the SubscriptionStoreView is presented, selecting Monthly, tap Subscribe, and nothing happens. Selecting either Yearly or Biannually, tap Subscribe and the Confirmation Dialog that triggers faceID/touchID will appear correctly. This happens in both Apps for TestFlight. I have a Manage Subscriptions button that uses: manageSubscriptionsSheet(isPresented:subscriptionGroupID:) I can change the subscription to Monthly in that manage subscriptions. However, if I switch to a Sandbox Apple Account, the "bug" described above does not happen. The Sandbox account when selecting Monthly and tap Subscribe will trigger the Confirmation Dialog (in both Apps). Not sure if my main account is "stuck" in some loop where it is trying to purchase Monthly in TestFlight but it is not completed. Has anyone ever encountered such a bug?
1
0
130
1w
In app purchases
Hey guys, This is a general question, but I have been working on an app for a while and I would like to to introduce IAP to the app. I have no clue where to start and chat GPT is no help. I also tried to vibe coding the IAP function and that didn't work out well. Any material or advice would help.
4
0
64
1w
how to handle verification step for in-app purchase?
a UK-based user is having trouble completing an in-app purchase. after going through the typical purchase flow (tapping the button to trigger the in-app purchase sheet, completing Face ID) they see this verification sheet appear over my app and have to go to their banking app to approve the purchase. after approving the purchase from their banking app, they tap "Payment confirmed on Mobile App" to close the sheet, but then see an alert that suggests the result is .userCancelled. the purchase does not seem to have completed. the user reports not being charged (despite numerous attempts). plus, i have a "restore purchases" function on App init that would've restored a purchase if it existed. i have implemented what i think is a typical Storekit.purchase() method (again, the message the user sees is for the .userCancelled case): func purchase(productId: String) async -> (Bool, String?) { guard let product = subscriptionProducts.first(where: { $0.id == productId }) else { return (false, "Product not found") } do { let result = try await product.purchase() switch result { case .success(let verification): switch verification { case .verified(let transaction): await transaction.finish() hasSubscription = true return (true, nil) case .unverified: return (false, "Transaction verification failed") } case .userCancelled: return (false, "No worries, take your time. 😌") case .pending: return (false, "Purchase is pending") u/unknown default: return (false, "Error purchasing product. If this keeps happening, please contact [email].") } } catch { return (false, "Error purchasing product: \(error.localizedDescription)") } } has anyone dealt with this issue? i was seeing an unusually high number of .userCancelled purchase events from users outside the US, and i'm wondering if some of them were genuine purchase attempts that were blocked by this verification step. 😕
2
0
79
1w
ExternalPurchaseCustomLink.token(for:) returns nil on one TestFlight device (while isEligible == true) — other device gets SERVICES token
I’m implementing StoreKit External Purchase Custom Links (EU) and so far it is really painful. I am running into a strange, device-specific issue. On 3/4 devices it works. On one device I never get a token at launch nor before a transaction. isEligible is true everywhere. All devices have versions 18.5 and are located in Germany. Info.plist: SKExternalPurchaseCustomLinkRegions is set to EU storefront codes and I have followed every step in the documentation: https://developer.apple.com/documentation/storekit/externalpurchasecustomlink Good device: At launch → ACQUISITION = nil, SERVICES = token present. Works consistently. Faulty device: At launch → ACQUISITION = nil, SERVICES = nil. Same before transaction. No token ever reaches my server from this device. isEligible is true on both devices. Any experts or help on the matter?
6
0
200
1w
Blocking using sandbox account testflight and app store
I set up a sandbox account to test in-app purchases in my development app. I went to settings > dev > sandbox accounts and updated my login in the app store. But I received a 'block'; I can no longer download new versions or anything from the store. I've already removed the sandbox account, and even using my real Apple ID, I can't use the functions.
0
0
20
1w
Cannot repurchase subscription SKU — StoreKit keeps returning old expired transaction
Some users cannot repurchase a subscription SKU after it has expired. Flow: User previously subscribed. User canceled and the subscription fully expired. After weeks, user reinstalls the app and taps the same SKU. StoreKit does not create a new purchase transaction. Instead, StoreKit always returns the old expired transaction in updatedTransactions. Therefore, the user is permanently unable to purchase the SKU again. We have already tried: Adding payment observer at app launch Calling finishTransaction for all transactions Clearing queue at startup SKReceiptRefreshRequest Server-side verifyReceipt Ensuring subscription is truly expired (not in grace/retry) Not calling restoreCompletedTransactions None of these resolved the issue. StoreKit still only sends the old transaction and never generates a new one. Expected behavior: A new purchase transaction should be created when user taps the expired subscription SKU. Actual behavior: StoreKit repeatedly pushes the old expired transaction, blocking new purchases. We can provide: Some users cannot repurchase a subscription SKU after it has expired. Flow: User previously subscribed. User canceled and the subscription fully expired. After weeks, user reinstalls the app and taps the same SKU. StoreKit does not create a new purchase transaction. Instead, StoreKit always returns the old expired transaction in updatedTransactions. Therefore, the user is permanently unable to purchase the SKU again. We have already tried: Adding payment observer at app launch Calling finishTransaction for all transactions Clearing queue at startup SKReceiptRefreshRequest Server-side verifyReceipt Ensuring subscription is truly expired (not in grace/retry) Not calling restoreCompletedTransactions None of these resolved the issue. StoreKit still only sends the old transaction and never generates a new one. Expected behavior: A new purchase transaction should be created when user taps the expired subscription SKU. Actual behavior: StoreKit repeatedly pushes the old expired transaction, blocking new purchases. We can provide: Affected user’s base64 receipt verifyReceipt full response Transaction logs (transactionIdentifier, original_transaction_id, productIdentifier, state) Please help investigate why StoreKit is not allowing a new subscription purchase. Affected user’s base64 receipt verifyReceipt full response Transaction logs (transactionIdentifier, original_transaction_id, productIdentifier, state) Please help investigate why StoreKit is not allowing a new subscription purchase.
1
0
137
2w
Multiple active subscriptions within the same subscription group
We have 2 monthly subscription tiers that are part of a subscription group, and always have been part of this group. Both are configured with a 1 month trial introductory offer. According to the documentation for auto-renewable subscriptions,: Users can subscribe to one subscription product per group at a time. And yet several users have managed to start trials of both products in this group simultaneously, which converted to paid subscriptions after the expiration of the trials, and now are being concurrently billed by Apple for both. How do we completely prevent this from happening?
1
0
39
2w