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

Can StoreKit products be observed with ObservableObject? Can I get notified when a users subscription has lapsed without polling Transaction.currentEntitlements?
I have an auto-renewable subscription. I have two methods helping me keep track of when they are expired @MainActor public func isPurchased(product: Product) async -> Bool { guard let state = await product.currentEntitlement else { return false } switch state { case .unverified(_, _): return false case .verified(let transaction): await transaction.finish() return isTransactionRelevant(transaction) } } private func isTransactionRelevant(_ transaction: Transaction) -> Bool { if let revocationDate = transaction.revocationDate { logger.error("Transaction verification failed: Transaction was revoked on \(revocationDate)") return false } if let expirationDate = transaction.expirationDate, expirationDate < Date() { logger.error("Transaction verification failed: Transaction expired on \(expirationDate)") return false } if transaction.isUpgraded { logger.error("Transaction verification failed: Transaction was upgraded") return false } logger.info("Transaction verification succeeded") return true } I also have this that I can call to get the latest state of purchases @MainActor public func updateStoreKitSubscriptionStatus() async { var currentProductsPurchased: [Product] = [] for await result in Transaction.currentEntitlements { if case .verified(let transaction) = result { if isTransactionRelevant(transaction) { if let product = products.first( where: { $0.id == transaction.productID }) { currentProductsPurchased.append(product) } } await transaction.finish() } } self.purchasedProducts = currentProductsPurchased } Right now when a subscription expires the user needs to manually do some action that triggers updateStoreKitSubscriptionStatus() as it appears that expirations do not come through in Transaction.updates. I am surprised there does not seem to be a better way. Does StoreKit not notify you somewhere that an auto-renewable subscription has expired? Can you observe it in an ObservableObject? Or do I need to just frequently poll Transaction.currentEntitlements even if I dont expect frequent updates?
0
0
117
Jul ’25
SubscriptionStoreView not showing free trial offer in release build
I'm using the SwiftUI view SubscriptionStoreView (https://developer.apple.com/documentation/storekit/subscriptionstoreview/) with a subscription group that has 2 subscriptions. I set up a free trial offer in App Store Connect (https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-introductory-offers-for-auto-renewable-subscriptions/). The storekit file in Xcode is synced with the App Store. In debug build, this works and appears correctly, showing the free trial offer: But in release build, the free trial offer is not shown: The code is very simple: SubscriptionStoreView(productIDs: [ "[PRODUCT ID FOR ANNUAL SUBSCRIPTION]", "[PRODUCT ID FOR BIMONTHLY SUBSCRIPTION]" ]) Does anyone have a solution? Thank you. (Xcode 16.3, macOS 15.5, iOS 18.5)
0
0
132
May ’25
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
106
Jul ’25
How to cancel Auto-renewable subscription bought in TestFlight?
I've read several topics on cancelling subscriptions in sandbox environment, but it seems to me that it could not be applied to TestFlight. I can cancel sandbox subscriptions through Settings > App Store > Sandbox account But since TestFlight does not use sandbox account I cannot cancel a sub from there. Also, TF purchase does not appear in the list of regular subscriptions (Settings > Profile > Media & Purchases). So my question is: is there any way to manually cancel auto-renewable subscription bought in TestFlight build of the app?
8
6
7.3k
4d
[StoreKit1] IAP Works in TestFlight but Fails During App Review (2.1 Rejection)
Hello Apple Developer Team, We're experiencing consistent IAP approval rejections under Guideline 2.1, despite successful TestFlight verification. Here's our detailed situation: Environment StoreKit 1 implementation Tested on iOS 18.5 or 18.6 devices Sandbox environment works perfectly Verification Steps Taken ✅ Confirmed all Product IDs match App Store Connect exactly ✅ Validated 10+ successful TestFlight transactions (attached screenshot samples) ✅ Verified banking/tax agreements are active Objective-C Code (StoreKit1 Implementation) - (void)buyProductId:(NSString *)pid AndSetGameOrderID:(NSString *)orderID{ if([SKPaymentQueue canMakePayments]){ if (!hasAddObserver) { [[SKPaymentQueue defaultQueue] addTransactionObserver:_neo]; hasAddObserver = YES; } self.neoOrderID = orderID; [[NSUserDefaults standardUserDefaults] setValue:orderID forKey:Pay_OrderId_Key]; self.productID = pid; NSArray * product = [[NSArray alloc]initWithObjects:self.productID, nil]; NSSet * nsset = [NSSet setWithArray:product]; SKProductsRequest * request = [[SKProductsRequest alloc]initWithProductIdentifiers:nsset]; request.delegate = self; [request start]; }else{ NSString * Err = @"Pembelian tidak diizinkan. Silakan aktifkan perizinan di pengaturan"; // UnitySendMessage("GameManager", "IAPPurchaseFailed", [Err UTF8String]); return; } } - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { NSArray * product = response.products; if ([product count] == 0) { [[SKPaymentQueue defaultQueue]removeTransactionObserver:_neo]; hasAddObserver = NO; NSString * Err = [NSString stringWithFormat:@"Err = 01, Item tidak ditemukan %@",self.productID]; // UnitySendMessage("GameManager", "IAPPurchaseFailed", [Err UTF8String]); return; } SKProduct * p = nil; for (SKProduct * pro in product) { if ([pro.productIdentifier isEqualToString:self.productID]){ p = pro; }else{ [request cancel]; [[SKPaymentQueue defaultQueue]removeTransactionObserver:_neo]; hasAddObserver = NO; NSString * Err = [NSString stringWithFormat:@"Err = 02, %@",self.productID]; // UnitySendMessage("GameManager", "IAPPurchaseFailed", [Err UTF8String]); return; } } SKMutablePayment * mPayment = [SKMutablePayment paymentWithProduct:p]; mPayment.applicationUsername = [NSString stringWithFormat:@"%@",self.neoOrderID]; if(!hasAddObserver){ [[SKPaymentQueue defaultQueue] addTransactionObserver:_neo]; hasAddObserver = YES; } [[SKPaymentQueue defaultQueue] addPayment:mPayment]; } - (void)request:(SKRequest *)request didFailWithError:(NSError *)error{ [[SKPaymentQueue defaultQueue]removeTransactionObserver:_neo]; hasAddObserver = NO; NSString * Err = [NSString stringWithFormat:@"Err = 0%ld %@", (long)error.code, self.productID]; // UnitySendMessage("GameManager", "IAPPurchaseFailed", [Err UTF8String]); } - (void)requestDidFinish:(SKRequest *)request{ } - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction{ for(SKPaymentTransaction *tran in transaction){ if (SKPaymentTransactionStatePurchased == tran.transactionState){ [self completeTransaction:tran]; }else if(SKPaymentTransactionStateFailed == tran.transactionState){ [self failedTransaction:tran]; } } } - (void)failedTransaction: (SKPaymentTransaction *)transaction { NSString * detail = [NSString stringWithFormat:@"%ld",(long)transaction.error.code]; // UnitySendMessage("GameManager", "IAPPurchaseFailed", [detail UTF8String]); [[SKPaymentQueue defaultQueue]removeTransactionObserver:_neo]; hasAddObserver = NO; [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; } - (void)completeTransaction:(SKPaymentTransaction *)transaction{ NSMutableDictionary * mdic = [NSMutableDictionary dictionary]; NSString * productIdentifier = transaction.payment.productIdentifier; NSData * _recep = nil; NSString * _receipt = @""; if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) { _recep = transaction.transactionReceipt; _receipt = [[NSString alloc]initWithData:_recep encoding:NSUTF8StringEncoding]; } else { _recep = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]]; _receipt = [_recep base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; } NSString * gameOrderid = [transaction payment].applicationUsername; if (gameOrderid == nil) { gameOrderid = [[NSUserDefaults standardUserDefaults] objectForKey:Pay_OrderId_Key]; } if(_receipt != nil && gameOrderid != nil){ mdic[@"orderid"] = gameOrderid; mdic[@"productid"] = productIdentifier; mdic[@"receipt"] = _receipt; }else{ [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; return; } NSData * data = [NSJSONSerialization dataWithJSONObject:mdic options:kNilOptions error:nil]; NSString * jsonString = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; if (hasAddObserver) { [[SKPaymentQueue defaultQueue] removeTransactionObserver:_neo]; hasAddObserver = NO; } // UnitySendMessage("GameManager", "IAPPurchaseSuecess", [jsonString UTF8String]); [self verifyReceipt:_recep completion:^(BOOL success, NSDictionary *response) { if (success) { NSLog(@"verify success"); // [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; [self verifySuecessDelTransactions]; } }]; } - (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue { for(SKPaymentTransaction *tran in queue.transactions){ if (SKPaymentTransactionStatePurchased == tran.transactionState){ [self completeTransaction:tran]; } } } - (void)verifySuecessDelTransactions{ SKPaymentQueue *paymentQueue = [SKPaymentQueue defaultQueue]; NSArray<SKPaymentTransaction *> *transactions = paymentQueue.transactions; if (transactions.count == 0) { return; } for (SKPaymentTransaction *transaction in transactions) { if (transaction.transactionState == SKPaymentTransactionStatePurchased || transaction.transactionState == SKPaymentTransactionStateRestored) { [paymentQueue finishTransaction:transaction]; } } }
1
0
165
Aug ’25
StoreKit2 caches local raw transactions and retrieval
FB19377002 I am looking to improve and review my subscription purchase handling logic, for the best user experience. Considering that StoreKit2 caches local raw transactions (in case user is offline), is it really necessary to persist "unlocked status" in UserDefaults or SwiftData Model or AppStorage? Are there significant delays when reading Transaction.currentEntitlements from locally stored cache, versus reading it from UserDefaults; or, as in the latest SKDemo example, even reading it from stored in SwiftData ? https://developer.apple.com/forums/thread/706450 I only have subscriptions ( I don't have noncosumable or consubale products). Do I still need to persist subscription status?
0
0
99
Aug ’25
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.7k
Jan ’26
IAP receipt validation fails with status code: 21002
I have implemented IAP. The purchases are successful. The refresh receipt is working fine, which then calls the requestDidFinish(_ request: SKRequest) delegate. I'm fetching the receipt url through 'Bundle.main.appStoreReceiptURL'. When I convert the receipt data in base64 string and send it to app store's sandbox api and try to validate the receipt, it fails giving status code : 21002.
2
0
266
Aug ’25
Help: IAP Works in Xcode but Not in TestFlight – Cannot Link IAP to Version for Review
Hi everyone, I've been going back and forth with Apple’s review team for over 10 days now, and I'm still unable to get my first In-App Purchase (IAP) working correctly. Here's what’s happening: ✅ The IAP works perfectly when I build and run directly from Xcode. ❌ However, when I test the app via TestFlight, tapping the purchase buttons does nothing—the IAP sheet doesn't appear. Key issue (I think): I believe the IAP hasn't been submitted properly for review. On App Store Connect, I cannot select the IAP under the “In-App Purchases” section of the version submission page. It's grayed out or not listed at all. As a result, Apple keeps rejecting my binary due to the IAP not being included in the review. What I’ve already done: Created the IAP (non-consumable) Set pricing and cleared all errors Checked Bundle ID, Product ID, and entitlements Added In-App Purchase capability to the app target Uploaded the binary via Xcode Waited multiple times for status updates My questions: What’s the correct process to link the IAP to a specific app version if it doesn't show up in the version page? Could this be an issue with App Store Connect metadata or approval timing, or am I missing something in Xcode/build settings? Is there any way to force re-sync the IAP so it appears when submitting the build? Has anyone resolved a similar issue recently? This process has been incredibly frustrating, and the feedback from the review team so far has been very vague. I would really appreciate any detailed insight or steps to ensure the IAP is submitted correctly and works on TestFlight. Thank you in advance!
1
0
133
Jun ’25
IAP working in StoreKitTest on XCODE but not in TestFlight – shows mapped error "Product not available"
Hey everyone, I'm currently preparing an older iOS app for App Store release that includes a non-consumable In‑App Purchase using StoreKit 2. Everything works perfectly in the StoreKitTest environment inside Xcode – the product loads, the purchase flow runs, the transaction verifies. However, when I run the same app through TestFlight, I always get the error: ❌ Product not available - mapped to Here’s what I’ve already checked: ✅ The product ID is correct and matches what’s in App Store Connect (case-sensitive). ✅ The IAP is created in App Store Connect and includes: Title Product ID Price Tier Screenshot for review ✅ The App Store "Paid Applications" agreement is active. ✅ The app is using the correct bundle ID. ✅ I'm using Product.products(for: [productID]) from StoreKit 2. ✅ I’ve implemented fallback and retry logic (e.g. reload after delay). ✅ All IAP logic is wrapped in @MainActor and async-safe. As the App got Rejected on Review, the IAP is also now in the Rejected Status. Now the IAP shows status: 🟠 "Developer Action Required" And App Review rejected the IAP with the message: "Your first In‑App Purchase must be submitted together with a new app version." But if I add the App to the Test again and therefore the IAP, then the app will get Rejected again for App Completeness, IAP does not work... What am I doing wrong here? :) Thanks a lot in advance Cheers, Niklas
1
0
133
Aug ’25
AppStore.sync() not restoring purchases
On an app that was using the old API for In-App Purchases (StoreKit 1). The app is already published on the App Store. The purchase is non-consumable. While trying to migrate to StoreKit 2, I'm unable to restore purchases. Specifically displaying and purchasing products works as expected, but when deleting and reinstalling the app, and then trying to restore purchases I can't do it. I'm trying to restore them using the new APIs but it doesn't seem to be working. What I have tried so far: I'm listening for transaction updates during the whole lifetime of the app, with: Task.detached { for await result in Transaction.updates { if case let .verified(safe) = result { } } } I have a button that calls this method, but other than prompting to log in again with the Apple ID it doesn't seem to have any effect at all: try? await AppStore.sync() This doesn't return any item for await result in Transaction.currentEntitlements { if case let .verified(transaction) = result { } } This doesn't return any item for await result in Transaction.all { if case let .verified(transaction) = result { } } As mentioned before I'm trying this after purchasing the item and deleting the app. So I'm sure it should be able to restore the purchase. Am trying this both with a Configuration.storekit file on the simulator, and without it on a real device, in the Sandbox Environment. Has anyone being able to restore purchases using StoreKit 2? PD: I already filed a feedback report on Feedback Assistant, but so far the only thing that they have replied is: Because StoreKit Testing in Xcode is a local environment, and the data is tied to the app, when you delete the app you're also deleting all the transaction data for that app in the Xcode environment. The code snippets provided are correct usage of the API. So yes, using a Configuration.storekit file won't work on restoring purchases, but if I can't restore them on the Sandbox Environment I'm afraid that this won't work once released, leaving my users totally unable to restore what they have already purchased.
3
0
1.9k
1w
(verifyreceipt) I cannot verify from the server whether the user's iap payment is successful or not
I have three questions about verify receipt I use this api (https://buy.itunes.apple.com/verifyReceipt)to verify receipt is success or not. But since last month, this interface has started to return an error(21002). I see this document (https://developer.apple.com/documentation/appstorereceipts/verifyreceipt) say its Deprecated. My question is, is the error suddenly returned recently because the interface has been deprecated or for some other reason? (I haven't modified my code about this recently) 2. I can not understand this document: (https://developer.apple.com/documentation/appstorereceipts/validating_receipts_on_the_device) Does this mean that in the new version, as long as the app returns a payment success (purchaseDetails.status == PurchaseStatus.purchased), the payment is guaranteed to be successful, and my server does not need to request payment result verification from Apple's server? 3. I try to use this (https://github.com/apple/app-store-server-library-java) to get TransactionInfo, but I dont konw to get Transaction status to know is success or not. my java server code : AppStoreServerAPIClient client = new AppStoreServerAPIClient(encodedKey, keyId, issuerId, bundleId, environment); TransactionInfoResponse response = client.getTransactionInfo(transactionId); (bug i can note get transaction status, how do i konw this Transaction is success or not)
0
0
82
Aug ’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
339
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
124
Aug ’25
SubscriptionStoreView Not Working
Hello, I have two auto-renewable subscriptions set up in App Store Connect. However, when I try to use SubscriptionStoreView, I receive the following error: Subscription store should include at least one auto-renewable subscription. I believe my .xcodeproj is misconfigured, as I created a new project since I wanted to start from scratch. What am I missing?
1
0
125
Jun ’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
229
Oct ’25
iOS misleading users with payment notification timing
I'm getting really frustrated with emails from my App users who believe they've been charged for a free in-app purchase when they haven't. My App offers many in-app purchases of digital items and I give 4 of these away for free to let users get comfortable with how it works in-app. Over the last couple of years I've had a steady increase in angry emails from users who accuse me of fraud by charging them for a free item. I couldn't figure out for a while what this was as they would leave a 1 star rating, delete the app and ignore my emails for more information. Recently I had someone a bit more patient engage and explain it to me. The purchase for some reason popped up on my notifications right when I bought the [Free Item in my app]. It was from a movie I bought and the bill was delayed. The timing of that notification is what is misleading users about the free in-app purchase. Can someone take note of this please and perhaps delay any payment notifications so they aren't sent when the in-app purchase is for FREE? Thanks!
2
0
1k
Jun ’25
Can StoreKit products be observed with ObservableObject? Can I get notified when a users subscription has lapsed without polling Transaction.currentEntitlements?
I have an auto-renewable subscription. I have two methods helping me keep track of when they are expired @MainActor public func isPurchased(product: Product) async -> Bool { guard let state = await product.currentEntitlement else { return false } switch state { case .unverified(_, _): return false case .verified(let transaction): await transaction.finish() return isTransactionRelevant(transaction) } } private func isTransactionRelevant(_ transaction: Transaction) -> Bool { if let revocationDate = transaction.revocationDate { logger.error("Transaction verification failed: Transaction was revoked on \(revocationDate)") return false } if let expirationDate = transaction.expirationDate, expirationDate < Date() { logger.error("Transaction verification failed: Transaction expired on \(expirationDate)") return false } if transaction.isUpgraded { logger.error("Transaction verification failed: Transaction was upgraded") return false } logger.info("Transaction verification succeeded") return true } I also have this that I can call to get the latest state of purchases @MainActor public func updateStoreKitSubscriptionStatus() async { var currentProductsPurchased: [Product] = [] for await result in Transaction.currentEntitlements { if case .verified(let transaction) = result { if isTransactionRelevant(transaction) { if let product = products.first( where: { $0.id == transaction.productID }) { currentProductsPurchased.append(product) } } await transaction.finish() } } self.purchasedProducts = currentProductsPurchased } Right now when a subscription expires the user needs to manually do some action that triggers updateStoreKitSubscriptionStatus() as it appears that expirations do not come through in Transaction.updates. I am surprised there does not seem to be a better way. Does StoreKit not notify you somewhere that an auto-renewable subscription has expired? Can you observe it in an ObservableObject? Or do I need to just frequently poll Transaction.currentEntitlements even if I dont expect frequent updates?
Replies
0
Boosts
0
Views
117
Activity
Jul ’25
SubscriptionStoreView not showing free trial offer in release build
I'm using the SwiftUI view SubscriptionStoreView (https://developer.apple.com/documentation/storekit/subscriptionstoreview/) with a subscription group that has 2 subscriptions. I set up a free trial offer in App Store Connect (https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-introductory-offers-for-auto-renewable-subscriptions/). The storekit file in Xcode is synced with the App Store. In debug build, this works and appears correctly, showing the free trial offer: But in release build, the free trial offer is not shown: The code is very simple: SubscriptionStoreView(productIDs: [ "[PRODUCT ID FOR ANNUAL SUBSCRIPTION]", "[PRODUCT ID FOR BIMONTHLY SUBSCRIPTION]" ]) Does anyone have a solution? Thank you. (Xcode 16.3, macOS 15.5, iOS 18.5)
Replies
0
Boosts
0
Views
132
Activity
May ’25
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.
Replies
1
Boosts
0
Views
106
Activity
Jul ’25
How to cancel Auto-renewable subscription bought in TestFlight?
I've read several topics on cancelling subscriptions in sandbox environment, but it seems to me that it could not be applied to TestFlight. I can cancel sandbox subscriptions through Settings > App Store > Sandbox account But since TestFlight does not use sandbox account I cannot cancel a sub from there. Also, TF purchase does not appear in the list of regular subscriptions (Settings > Profile > Media & Purchases). So my question is: is there any way to manually cancel auto-renewable subscription bought in TestFlight build of the app?
Replies
8
Boosts
6
Views
7.3k
Activity
4d
[StoreKit1] IAP Works in TestFlight but Fails During App Review (2.1 Rejection)
Hello Apple Developer Team, We're experiencing consistent IAP approval rejections under Guideline 2.1, despite successful TestFlight verification. Here's our detailed situation: Environment StoreKit 1 implementation Tested on iOS 18.5 or 18.6 devices Sandbox environment works perfectly Verification Steps Taken ✅ Confirmed all Product IDs match App Store Connect exactly ✅ Validated 10+ successful TestFlight transactions (attached screenshot samples) ✅ Verified banking/tax agreements are active Objective-C Code (StoreKit1 Implementation) - (void)buyProductId:(NSString *)pid AndSetGameOrderID:(NSString *)orderID{ if([SKPaymentQueue canMakePayments]){ if (!hasAddObserver) { [[SKPaymentQueue defaultQueue] addTransactionObserver:_neo]; hasAddObserver = YES; } self.neoOrderID = orderID; [[NSUserDefaults standardUserDefaults] setValue:orderID forKey:Pay_OrderId_Key]; self.productID = pid; NSArray * product = [[NSArray alloc]initWithObjects:self.productID, nil]; NSSet * nsset = [NSSet setWithArray:product]; SKProductsRequest * request = [[SKProductsRequest alloc]initWithProductIdentifiers:nsset]; request.delegate = self; [request start]; }else{ NSString * Err = @"Pembelian tidak diizinkan. Silakan aktifkan perizinan di pengaturan"; // UnitySendMessage("GameManager", "IAPPurchaseFailed", [Err UTF8String]); return; } } - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { NSArray * product = response.products; if ([product count] == 0) { [[SKPaymentQueue defaultQueue]removeTransactionObserver:_neo]; hasAddObserver = NO; NSString * Err = [NSString stringWithFormat:@"Err = 01, Item tidak ditemukan %@",self.productID]; // UnitySendMessage("GameManager", "IAPPurchaseFailed", [Err UTF8String]); return; } SKProduct * p = nil; for (SKProduct * pro in product) { if ([pro.productIdentifier isEqualToString:self.productID]){ p = pro; }else{ [request cancel]; [[SKPaymentQueue defaultQueue]removeTransactionObserver:_neo]; hasAddObserver = NO; NSString * Err = [NSString stringWithFormat:@"Err = 02, %@",self.productID]; // UnitySendMessage("GameManager", "IAPPurchaseFailed", [Err UTF8String]); return; } } SKMutablePayment * mPayment = [SKMutablePayment paymentWithProduct:p]; mPayment.applicationUsername = [NSString stringWithFormat:@"%@",self.neoOrderID]; if(!hasAddObserver){ [[SKPaymentQueue defaultQueue] addTransactionObserver:_neo]; hasAddObserver = YES; } [[SKPaymentQueue defaultQueue] addPayment:mPayment]; } - (void)request:(SKRequest *)request didFailWithError:(NSError *)error{ [[SKPaymentQueue defaultQueue]removeTransactionObserver:_neo]; hasAddObserver = NO; NSString * Err = [NSString stringWithFormat:@"Err = 0%ld %@", (long)error.code, self.productID]; // UnitySendMessage("GameManager", "IAPPurchaseFailed", [Err UTF8String]); } - (void)requestDidFinish:(SKRequest *)request{ } - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction{ for(SKPaymentTransaction *tran in transaction){ if (SKPaymentTransactionStatePurchased == tran.transactionState){ [self completeTransaction:tran]; }else if(SKPaymentTransactionStateFailed == tran.transactionState){ [self failedTransaction:tran]; } } } - (void)failedTransaction: (SKPaymentTransaction *)transaction { NSString * detail = [NSString stringWithFormat:@"%ld",(long)transaction.error.code]; // UnitySendMessage("GameManager", "IAPPurchaseFailed", [detail UTF8String]); [[SKPaymentQueue defaultQueue]removeTransactionObserver:_neo]; hasAddObserver = NO; [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; } - (void)completeTransaction:(SKPaymentTransaction *)transaction{ NSMutableDictionary * mdic = [NSMutableDictionary dictionary]; NSString * productIdentifier = transaction.payment.productIdentifier; NSData * _recep = nil; NSString * _receipt = @""; if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) { _recep = transaction.transactionReceipt; _receipt = [[NSString alloc]initWithData:_recep encoding:NSUTF8StringEncoding]; } else { _recep = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]]; _receipt = [_recep base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; } NSString * gameOrderid = [transaction payment].applicationUsername; if (gameOrderid == nil) { gameOrderid = [[NSUserDefaults standardUserDefaults] objectForKey:Pay_OrderId_Key]; } if(_receipt != nil && gameOrderid != nil){ mdic[@"orderid"] = gameOrderid; mdic[@"productid"] = productIdentifier; mdic[@"receipt"] = _receipt; }else{ [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; return; } NSData * data = [NSJSONSerialization dataWithJSONObject:mdic options:kNilOptions error:nil]; NSString * jsonString = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; if (hasAddObserver) { [[SKPaymentQueue defaultQueue] removeTransactionObserver:_neo]; hasAddObserver = NO; } // UnitySendMessage("GameManager", "IAPPurchaseSuecess", [jsonString UTF8String]); [self verifyReceipt:_recep completion:^(BOOL success, NSDictionary *response) { if (success) { NSLog(@"verify success"); // [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; [self verifySuecessDelTransactions]; } }]; } - (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue { for(SKPaymentTransaction *tran in queue.transactions){ if (SKPaymentTransactionStatePurchased == tran.transactionState){ [self completeTransaction:tran]; } } } - (void)verifySuecessDelTransactions{ SKPaymentQueue *paymentQueue = [SKPaymentQueue defaultQueue]; NSArray<SKPaymentTransaction *> *transactions = paymentQueue.transactions; if (transactions.count == 0) { return; } for (SKPaymentTransaction *transaction in transactions) { if (transaction.transactionState == SKPaymentTransactionStatePurchased || transaction.transactionState == SKPaymentTransactionStateRestored) { [paymentQueue finishTransaction:transaction]; } } }
Replies
1
Boosts
0
Views
165
Activity
Aug ’25
SKOverlay App Store Connect Attribution Missing?
I am trying use SKOverlay to promote an app, and I currently have the campaignToken field populated. However, even when installs happen from the SKOverlay, nothing shows up under that campaign token in App Store Connect. Is there something that I'm missing? I don't have the providerToken set.
Replies
0
Boosts
0
Views
120
Activity
May ’25
StoreKit2 caches local raw transactions and retrieval
FB19377002 I am looking to improve and review my subscription purchase handling logic, for the best user experience. Considering that StoreKit2 caches local raw transactions (in case user is offline), is it really necessary to persist "unlocked status" in UserDefaults or SwiftData Model or AppStorage? Are there significant delays when reading Transaction.currentEntitlements from locally stored cache, versus reading it from UserDefaults; or, as in the latest SKDemo example, even reading it from stored in SwiftData ? https://developer.apple.com/forums/thread/706450 I only have subscriptions ( I don't have noncosumable or consubale products). Do I still need to persist subscription status?
Replies
0
Boosts
0
Views
99
Activity
Aug ’25
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.
Replies
21
Boosts
15
Views
1.7k
Activity
Jan ’26
IAP receipt validation fails with status code: 21002
I have implemented IAP. The purchases are successful. The refresh receipt is working fine, which then calls the requestDidFinish(_ request: SKRequest) delegate. I'm fetching the receipt url through 'Bundle.main.appStoreReceiptURL'. When I convert the receipt data in base64 string and send it to app store's sandbox api and try to validate the receipt, it fails giving status code : 21002.
Replies
2
Boosts
0
Views
266
Activity
Aug ’25
Help: IAP Works in Xcode but Not in TestFlight – Cannot Link IAP to Version for Review
Hi everyone, I've been going back and forth with Apple’s review team for over 10 days now, and I'm still unable to get my first In-App Purchase (IAP) working correctly. Here's what’s happening: ✅ The IAP works perfectly when I build and run directly from Xcode. ❌ However, when I test the app via TestFlight, tapping the purchase buttons does nothing—the IAP sheet doesn't appear. Key issue (I think): I believe the IAP hasn't been submitted properly for review. On App Store Connect, I cannot select the IAP under the “In-App Purchases” section of the version submission page. It's grayed out or not listed at all. As a result, Apple keeps rejecting my binary due to the IAP not being included in the review. What I’ve already done: Created the IAP (non-consumable) Set pricing and cleared all errors Checked Bundle ID, Product ID, and entitlements Added In-App Purchase capability to the app target Uploaded the binary via Xcode Waited multiple times for status updates My questions: What’s the correct process to link the IAP to a specific app version if it doesn't show up in the version page? Could this be an issue with App Store Connect metadata or approval timing, or am I missing something in Xcode/build settings? Is there any way to force re-sync the IAP so it appears when submitting the build? Has anyone resolved a similar issue recently? This process has been incredibly frustrating, and the feedback from the review team so far has been very vague. I would really appreciate any detailed insight or steps to ensure the IAP is submitted correctly and works on TestFlight. Thank you in advance!
Replies
1
Boosts
0
Views
133
Activity
Jun ’25
IAP working in StoreKitTest on XCODE but not in TestFlight – shows mapped error "Product not available"
Hey everyone, I'm currently preparing an older iOS app for App Store release that includes a non-consumable In‑App Purchase using StoreKit 2. Everything works perfectly in the StoreKitTest environment inside Xcode – the product loads, the purchase flow runs, the transaction verifies. However, when I run the same app through TestFlight, I always get the error: ❌ Product not available - mapped to Here’s what I’ve already checked: ✅ The product ID is correct and matches what’s in App Store Connect (case-sensitive). ✅ The IAP is created in App Store Connect and includes: Title Product ID Price Tier Screenshot for review ✅ The App Store "Paid Applications" agreement is active. ✅ The app is using the correct bundle ID. ✅ I'm using Product.products(for: [productID]) from StoreKit 2. ✅ I’ve implemented fallback and retry logic (e.g. reload after delay). ✅ All IAP logic is wrapped in @MainActor and async-safe. As the App got Rejected on Review, the IAP is also now in the Rejected Status. Now the IAP shows status: 🟠 "Developer Action Required" And App Review rejected the IAP with the message: "Your first In‑App Purchase must be submitted together with a new app version." But if I add the App to the Test again and therefore the IAP, then the app will get Rejected again for App Completeness, IAP does not work... What am I doing wrong here? :) Thanks a lot in advance Cheers, Niklas
Replies
1
Boosts
0
Views
133
Activity
Aug ’25
AppStore.sync() not restoring purchases
On an app that was using the old API for In-App Purchases (StoreKit 1). The app is already published on the App Store. The purchase is non-consumable. While trying to migrate to StoreKit 2, I'm unable to restore purchases. Specifically displaying and purchasing products works as expected, but when deleting and reinstalling the app, and then trying to restore purchases I can't do it. I'm trying to restore them using the new APIs but it doesn't seem to be working. What I have tried so far: I'm listening for transaction updates during the whole lifetime of the app, with: Task.detached { for await result in Transaction.updates { if case let .verified(safe) = result { } } } I have a button that calls this method, but other than prompting to log in again with the Apple ID it doesn't seem to have any effect at all: try? await AppStore.sync() This doesn't return any item for await result in Transaction.currentEntitlements { if case let .verified(transaction) = result { } } This doesn't return any item for await result in Transaction.all { if case let .verified(transaction) = result { } } As mentioned before I'm trying this after purchasing the item and deleting the app. So I'm sure it should be able to restore the purchase. Am trying this both with a Configuration.storekit file on the simulator, and without it on a real device, in the Sandbox Environment. Has anyone being able to restore purchases using StoreKit 2? PD: I already filed a feedback report on Feedback Assistant, but so far the only thing that they have replied is: Because StoreKit Testing in Xcode is a local environment, and the data is tied to the app, when you delete the app you're also deleting all the transaction data for that app in the Xcode environment. The code snippets provided are correct usage of the API. So yes, using a Configuration.storekit file won't work on restoring purchases, but if I can't restore them on the Sandbox Environment I'm afraid that this won't work once released, leaving my users totally unable to restore what they have already purchased.
Replies
3
Boosts
0
Views
1.9k
Activity
1w
Testing price change for the auto-renewing subscriptions
We are considering a price change for the auto-renewing subscriptions we currently offer in a Production environment and have made system modifications to our servers. We would like to implement a price change for purchases made through our SANDBOX Apple account in order to test if our system is capable of handling the price change.
Replies
1
Boosts
0
Views
75
Activity
Aug ’25
how does auto sync App server price to IAP price
After I configured a consumable subscription(In App Purchases) on the apple platform, when I change the price in the App's server, is there any existing apple Api support to change the price on the apple platform? Of course, I know I need to resubmit.
Replies
0
Boosts
0
Views
76
Activity
Jun ’25
(verifyreceipt) I cannot verify from the server whether the user's iap payment is successful or not
I have three questions about verify receipt I use this api (https://buy.itunes.apple.com/verifyReceipt)to verify receipt is success or not. But since last month, this interface has started to return an error(21002). I see this document (https://developer.apple.com/documentation/appstorereceipts/verifyreceipt) say its Deprecated. My question is, is the error suddenly returned recently because the interface has been deprecated or for some other reason? (I haven't modified my code about this recently) 2. I can not understand this document: (https://developer.apple.com/documentation/appstorereceipts/validating_receipts_on_the_device) Does this mean that in the new version, as long as the app returns a payment success (purchaseDetails.status == PurchaseStatus.purchased), the payment is guaranteed to be successful, and my server does not need to request payment result verification from Apple's server? 3. I try to use this (https://github.com/apple/app-store-server-library-java) to get TransactionInfo, but I dont konw to get Transaction status to know is success or not. my java server code : AppStoreServerAPIClient client = new AppStoreServerAPIClient(encodedKey, keyId, issuerId, bundleId, environment); TransactionInfoResponse response = client.getTransactionInfo(transactionId); (bug i can note get transaction status, how do i konw this Transaction is success or not)
Replies
0
Boosts
0
Views
82
Activity
Aug ’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
339
Activity
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.
Replies
0
Boosts
1
Views
124
Activity
Aug ’25
SubscriptionStoreView Not Working
Hello, I have two auto-renewable subscriptions set up in App Store Connect. However, when I try to use SubscriptionStoreView, I receive the following error: Subscription store should include at least one auto-renewable subscription. I believe my .xcodeproj is misconfigured, as I created a new project since I wanted to start from scratch. What am I missing?
Replies
1
Boosts
0
Views
125
Activity
Jun ’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
229
Activity
Oct ’25
iOS misleading users with payment notification timing
I'm getting really frustrated with emails from my App users who believe they've been charged for a free in-app purchase when they haven't. My App offers many in-app purchases of digital items and I give 4 of these away for free to let users get comfortable with how it works in-app. Over the last couple of years I've had a steady increase in angry emails from users who accuse me of fraud by charging them for a free item. I couldn't figure out for a while what this was as they would leave a 1 star rating, delete the app and ignore my emails for more information. Recently I had someone a bit more patient engage and explain it to me. The purchase for some reason popped up on my notifications right when I bought the [Free Item in my app]. It was from a movie I bought and the bill was delayed. The timing of that notification is what is misleading users about the free in-app purchase. Can someone take note of this please and perhaps delay any payment notifications so they aren't sent when the in-app purchase is for FREE? Thanks!
Replies
2
Boosts
0
Views
1k
Activity
Jun ’25