StoreKit

RSS for tag

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

Posts under StoreKit tag

200 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

Seeing ASDErrorDomain Code=603 "Request throttled" for SKReceiptRefreshRequest on iOS 16.1 only
I am using local receipt validation and the SKReceiptRefreshRequest API to restore purchases. When my iOS 16.1 users tap "restore purchase", the call fails. Notably, it does not ask the users to log into their iTunes account (it normally does) and my app logs the following error: <SKReceiptRefreshRequest: 0x281b0ad20>: Finished refreshing receipt with error: Error Domain=ASDErrorDomain Code=603 "Request throttled" UserInfo={NSLocalizedFailureReason=Unified receipt is valid and current, NSLocalizedDescription=Request throttled, AMSServerErrorCode=0} These errors started showing up after the release of 16.1 and appear to be limited to that specific version of iOS. The relevant code has not changed in years and I have iOS 15 and iOS 16.0 users who are currently able to restore purchases without issue. Also, I am not able to reproduce the issue in the sandbox but I am able to do so in production. I'm a bit at a loss. Why would my request be "throttled" and, further, why only on iOS 16.1 and only in production? Any thoughts on what could be happening here? Any help is much appreciated. Thank you!
2
3
1.8k
Sep ’23
How to test IAP in the Sandbox environment?
I've been reading through the documents and I feel like each particular doc jumps over the piece of info that I need. It explains how to create a Sandbox Test User and I've done that. I did plenty of IAP work many years ago, but things have changed enough that it's not working as I expect. When I read the docs, I feel like I can't find the missing piece. When I test in a simulator with Xcode, it does my IAP transactions in the Xcode environment. That makes sense. https://developer.apple.com/documentation/storekit/in-app_purchase/testing_in-app_purchases_with_sandbox This page says: To run your app using your Sandbox Apple ID, build and run your app from Xcode. But when I test on a physical device installing directly from Xcode, it also does my IAP transactions in the Xcode environment. I thought this would put me in the Sandbox environment, but it didn't. The initial purchase view controller didn't reference the Sandbox at all and the "You're All Set" alert shows Environment: Xcode. When I put a test build into TestFlight and test using that version, my IAP transactions are happening in production using my real Apple Id (but I'm not actually charged for any transactions). I assumed that testing in TestFlight would give me the Sandbox environment, but it happens in the Production environment. (The purchase view controller doesn't show Sandbox and the "You're All Set" alert doesn't show Sandbox.) So, how do I test in the Sandbox? Every way I try to test either puts me in the Xcode environment or the Production Environment. What am I missing? I've logged out of my real Apple id on my phone and then logged in with the Sandbox User credentials I created in AppStore Connect. But when I tried to test the app, by installing from Xcode it still says I'm in the Xcode test environment. When I create transactions, those transactions show up in the Xcode Transaction Manager. How do I test in the Sandbox environment?
6
4
2.5k
Aug ’23
Why I can't see my teams while creating StoreKit Configuration File
I am trying to add In-app purchases to my app. I tried to create StoreKit Configuration File but after checking "Sync this file with an app in App Store Connect" I wanted to pick my team but the only options I had were "None" or "Ad an account...". And answering upcoming question: Yes I have team. Please help.
3
1
1.4k
Jun ’24
Unfinished transactions not being emitted on start of app
I'm using the iOS simulator with a StoreKit configuration file. I can see that there have been transactions while the app has been closed, but my StoreKit 2 listener is never called with those updates to be able to finish them When I open my app from a cold start. I've added a listener on application(_:didFinishLaunching:launchOptions:) like this: func startObservingTransactions() { task = Task(priority: .background) { for await result in Transaction.updates { if case .verified(let transaction) = result { await transaction.finish() } } } } But the Transaction.updates loop never gets called (have added breakpoints to check). It's only ever called when a purchase is made, or subsequent transaction renewals when the app is open. Only then it will get the previously unfinished transactions. Steps to reproduce: Create an app with a StoreKit config file (with sped up transactions) to purchase an item Make a purchase then quit the app Wait for a bit for more transactions to be made while the app is closed. Open the app from a cold start and none of the transactions will be finished by the listener in your app. Cancel the subscription via the transaction manager. Close and open the app from a cold start. The first transaction will be finished by the listener but none of the others will be. In Apple's docs it says If your app has unfinished transactions, the listener receives them immediately after the app launches Why is this not the case?
14
2
3.4k
3w
One-time purchase with free trial
Dear all, This is my first post in this forum - and, in fact, my first app, too! I'm glad to be here, and thanks in advance for your help. I'm looking to offer an app for a one-time payment. I'd also like people to be able to try the app for a week. It seems that the "Pricing and Availability" section in App Store Connect is not the right place to configure this kind of offer. It does allow me to set a one-time price, but I cannot find a trial-period there (or am I missing something?) Two different strategies seem possible here: Using in-app-purchases: make the actual app free, but ask users after a week to buy a non-consumable IAP. The problem with that: I need to verify that the app has been installed for seven days ... even if it has been uninstalled at some point. Using subscriptions: There is a "free trial" option for subscriptions. But after that free trial, subscriptions are being payed periodically. I'd rather have the user only pay once for lifetime-access. Some apps seem to use strategy 1 - I believe the "Lap swim" app does. But still it seems like a bit of a hack - is there a more elegant way to achieve this?
5
1
5.4k
Apr ’24
StoreKit2: .purchase() not working after expiry of subscription in app, but renewing in AppStore sandbox does...
Hi, thanks for reading my question. I need help with some odd behaviour with product.purchase() not triggering a confirmation dialog after a subscription has expired and trying to purchase it again. Seeing this in iOS 16.2 and 15.7.2 (haven't tried any other versions) on actual devices, not in simulator. I'm using a sandbox user on the sandbox environment (not using the local store kit config file testing option). Using a newly created sandbox user, first subscription purchase goes through just fine, dialog box pops up, login with sandbox user, get confirmation of purchase and then Transaction.currentEntitlements has one item as expected. It auto renews for 12 times (each time Transaction.currentEntitlements contains the correct results) and then expires, as expected for sandbox. Transaction.currentEntitlements is then also empty, as expected. All good so far. Now I want to test purchasing it again...Call product.purchase() again to renew/start a new subscription and nothing happens, no confirm purchase dialog box pops up at all. The purchase function simply exits BUT returns success (as in the following gets called) but in self.updatePurchasedProducts(), Transaction.currentEntitlements is empty. case let .success(.verified(transaction)):      // Successful purchase       await transaction.finish()      await self.updatePurchasedProducts() if I instead go to Settings->App Store->Sandbox User-> Manage Subscriptions and renew the subscription there, instead of in my app, then Transaction.currentEntitlements has a new entry and all is good again. Alternatively, if I create yet another new sandbox user and logout of the old one I was using, I am once again able to purchase from within the app, so .purchase() once again works as normal. Is there something I am missing about expired subscriptions and trying to purchase them again in the app? Is this a sandbox issue and in production I'll have no problem? The sandbox user has purchasing enabled in Settings->App Store. I've also tried calling AppStore.sync() (which is in my "Restore Purchase" button) before calling product.purchase() after the subscription stops renewing, expires and this issue comes up, doesn't resolve it. Also have a less important question, the initial call to product.purchase(), the one that works as expected, has a bit of a delay before the confirmation dialog pops up, a few seconds, which will probably result in the user clicking the buy button again thinking it didn't work. Is a bit of a delay normal for sandbox? Will it be ok in production? When it fails, and I have to renew in Settings->AppStore->Sandbox user, there's also a bit of a delay after I return to my app, 5-15 or so seconds, before the transaction observer fires and currentEntitlements is checked again, is there a way to reduce this delay? Thank you! Colin @MainActor class IAPManager: NSObject, ObservableObject {  // removed other functions.....   func purchase(_ product: Product) async throws {    let result = try await product.purchase()     switch result {    case let .success(.verified(transaction)):      // Successful purchase       await transaction.finish()      await self.updatePurchasedProducts()    case let .success(.unverified(_, error)):       break     case .pending:       break     case .userCancelled:       break     @unknown default:       break   } }  func updatePurchasedProducts() async {     for await result in Transaction.currentEntitlements {       guard case .verified(let transaction) = result else {         continue       }       if transaction.revocationDate == nil {         self.purchasedProductIDs.insert(transaction.productID)       } else {         self.purchasedProductIDs.remove(transaction.productID)       }     }   } }
11
8
6.8k
Oct ’23
How to know when user upgrades/downgraded/crossgrades their subscription on the client
Hey, in our app we show post-purchase flow when a user purchases a subscription and its appearance should depend on the type of purchase: upgrade, downgrade or crossgrade. I found a way how to get this type on the backend side but can not figure out how to get this within the app. I see that Transaction has isUpgraded property but it is always false even if I move from a lower service plan to a higher one. So, I have two questions: Is this actually possible to know on the client when the user upgrades, downgrades or crossgrades? If yes, then how? Thanks
4
1
1.6k
Dec ’23
Runtime error when using StoreKit 2
When performing StoreKit 2 operations in my Mac app, even very simple ones like AppStore.sync() a small but steady percentage of users experiences the following error: systemError(Error Domain=NSCocoaErrorDomain Code=4097 \"connection to service named com.apple.storekitagent\" UserInfo={NSDebugDescription=connection to service named com.apple.storekitagent}) Rebooting or re-downloading the App from the Mac App Store, as well as signing out and in again on the Mac App Store does not solve the issue. Strangely enough all of the affected users I'm aware of were initially able to perform In-App purchases via the said app. Without any updates or anything else however suddenly after a restart of the app the above error gets thrown by the system. As I have not found any way to solve this issue yet and the said customers are consequently left without service even though they are paying customers, I am desperate to find help with this issue. Given the simplicity of the AppStore.sync() method for example I can't imagine the solution to lie in code, but I might of course be wrong. Thank you, Bastian
2
0
974
Dec ’23
presentCodeRedemptionSheet issue
Hi I'm using presentCodeRedemptionSheet() method to display a sheet that enables users to redeem subscription offer codes. When calling the SKPaymentQueue.default().presentCodeRedemptionSheet() method on real app store app it will present redemption sheet and after entering the offer code in it is displaying screen where Cancel and Redeem buttons do not work. As seen in the attached picture. What could be the reason for this and what solutions can be found to solve the problem? Please someone help it is really frustrating. Environment: iOS 16.3
8
2
1.8k
Jan ’24
Suddenly receiving error 21002 from verifyReceipt endpoint for sandbox receipt
We started getting error code 21002 from the verifyReceipt endpoint today for any receipt passed to it: {     "status": 21002 } I have created a new sandbox tester, and cleared and reinstalled the application before attempting the purchase. I'm seeing this response through RevenueCat's receipt validation tool (https://www.revenuecat.com/app-store-receipt-validation/) and from a direct cURL command: curl --location --request POST 'https://sandbox.itunes.apple.com/verifyReceipt' \ --header 'content-type: application/json' \ --data-raw '{     "receipt-data":"{RECEIPT_DATA}",     "password":"{SECRET}",     "exclude-old-transactions":"false" }' This was working as of yesterday, but seems to be failing for anyone who has tried today. Has anyone else run into issues with this?
28
7
7k
1w
StoreKit 2 - Is it necessary to finish unverified transactions?
The sample code provided in https://developer.apple.com/wwdc21/10114 doesn't appear to call finish() on unverified transactions, and I haven't been able to find any documentation regarding what to do with unfinished transactions. However, Apple has always emphasized the importance of finishing transactions, and since a transaction object is provided even with the unverified state, I'd love some guidance!
2
0
1.5k
Dec ’23
Clarification on the use of webOrderLineItemID vs. id for managing auto-renewable subscriptions
I am working on implementing in-app purchases for my iOS app, specifically auto-renewable subscriptions. I've been trying to understand the differences between transaction.webOrderLineItemID and transaction.id and how they can be used in managing subscriptions. Both of these properties seem to provide unique identifiers for transactions, but I am unclear about the specific benefits of using webOrderLineItemID over id. Can you please provide clarification on the following points? What are the exact use cases where using webOrderLineItemID is more beneficial than id when managing auto-renewable subscriptions? Can different transactions have the same value for webOrderLineItemID? If not, how does it provide additional granularity or context compared to id? I appreciate any insights you can provide on this topic, as I want to ensure that I am using the appropriate identifiers for managing auto-renewable subscriptions in my app. Thank you!
1
0
1.1k
Feb ’24
Promo Codes for Mac Catalyst Apps
I have looked high and low all and cannot find an answer or solution. I have an app that is primarily used by Mac users. I am looking to implement promo codes in the coming weeks. The presentCodeRedemptionSheet call does not work for Mac Catalyst apps saying "This function doesn’t affect Mac apps built with Mac Catalyst" in the documentation. AppStore.presentOfferCodeRedeemSheet also does not work for Mac Catalyst. If it is the case that you can't redeem in-app, that is fine if I can direct users to the Mac App Store to redeem promo codes, but I cannot find a way to do that either. I can only find a route to redeem gift cards. I have also tried clicking on a specific promo code link (something like https://apps.apple.com/redeem?ctx=offercodes&id=00000000&code=PROMOCODE) and that just redirects to the Gift Card redemption screen in the Mac App Store. So is there any way for a Mac only user (a user that does not have an iPhone or iPad) to use app promo codes? Thanks!
1
0
676
Oct ’23
No way to close the page for subscription Offer Code processing invoked by "SKPaymentQueue.default().presentCodeRedemptionSheet()".
Problem was observed on the published App because the IAP offer code processing is only done on the published App. I implemented the ".presentCodeRedemptionSheet()" command by tapping Offer Code button. Then the typed in offer code was processed by this page and the offer was successfully purchased from AppStore. After detecting the purchased status, executed "SKPaymentQueue.default().finishTransaction(transaction)". I thought that finishTransaction() would close the offer code processing page that is not written in document. However the page for offer code processing page was still displayed and was never closed. The only way to close the input page for offer code is to manually tapping "Cancel" button. Since there is no way to close the offer code processing page, I added ".popToRootViewController(animated: true)" in order to close all pages and views after detection of purchased status but failed to close the offer code processing page managed by Apple. I need to know how to programmatically close the offer code processing page invoked by ".presentCodeRedemptionSheet()". I got no response from FeedBackAssistant and Apple Developer Technical Support. Thank you.
4
0
402
3w
App Store Server Notifications not always sent to my server
We have implemented an auto-renewing subscription as an in-app-purchase for our iOS application. We are consuming the App Store Server Notifications for subscription transactions in order to update the user's account (and thereby maintain their 'Pro' access to our application). Sometimes those notifications never come to our server, and there is no evidence that they were even attempted to be sent to us. We have had some users report to our Customer Support team that they have successfully made a purchase of the subscription, but that they were not granted 'Pro' access. For the large majority of users this is not happening and all is well, but for some users the notifications just never come from the App Store Server API. We keep a record of all notifications that we receive from the App Store, and for these users we never received the "SUBSCRIBED" event. We have checked the Notification History API and there are no reports of any failure to send notifications to our server. We have checked our server logs for any sign of failure to receive incoming web requests, and there is no sign of these missing notifications. We have verified that our server supports ATS. We are keeping the transaction.originalID for all our users who are subscribed to the auto-renewing subscription. We have used this value to do some lookups into the transaction history and subscription status of the users who are being affected. Here is an example of our findings from those lookups: From the transaction history endpoint, we received an error: “Invalid transaction identifier”. From the subscription status endpoint, we received a response with the information for that user's active, valid subscription. We never received any App Store Server Notifications about this user’s subscription, and the transaction history API tells us it is an invalid transaction ID. We believe that the fact that the subscription status API returned the information showing that this user’s subscription is active and valid, and that the notification history API shows no sign of a failure to send us notifications about that subscription, shows that the App Store Server API never attempted to send us any notification for this user’s subscription. The same is true for a significant number of other users of our service. Can anyone help us determine what is going on, and how to best support these customers? It seems as though there was never an attempt to send these notifications to our server, but our users provide proof that they do in fact have an active subscription, for which they have paid (receipt email from Apple with a valid order ID).
8
7
2.4k
Oct ’23
Transaction fails with ASDServerErrorDomain Code=3504 in production
Suddenly in-app purchases of already productive IAPs are failing in the production with the error code: NSUnderlyingError=0x283b42e20 {Error Domain=ASDServerErrorDomain Code=3504 "找不到此项目。" UserInfo={NSLocalizedDescription=找不到此项目。}}} Environment: Production OS Version:iOS 15.6.1 I confirm that there have been no prohibited sales areas for the product I want to know What does Code 3504 represent? Where can I find documentation related to error codes?
10
4
2.0k
Jun ’24
In-App Purchase Restore - No observers found
When I execute a restore on my in-app purchase I'm getting a warning, however the restore is successfully executed. I think this is something new with Xcode 14.3. My test device is running iOS 16.4 This is the warning: <SKPaymentQueue: 0x283708a80>: No observers found that respond to "paymentQueue:shouldAddStorePayment:forProduct:", will not check for purchase intents It fires at this point in the code. If I comment out the first line, I don't get the warning however, the restore doesn't execute. Is anybody else seeing this or do I have something wrong in my code? I know it's only a warning but any help would be appreciated. @IBAction func restoreButtonTapped(_ sender: UIBarButtonItem) { SKPaymentQueue.default().add(self) SKPaymentQueue.default().restoreCompletedTransactions() } I've included the rest of the code just for a completeness. func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { for transaction in transactions { switch transaction.transactionState { case .purchasing: //print("Purchase in progress...") break case .purchased: //print("Purchase Successful") SKPaymentQueue.default().finishTransaction(transaction) //print("Transaction Complete") // Hide the restore button navigationItem.setRightBarButton(nil, animated: true) //Set the BaseVerion in the Db to true IAPHandler.set_BaseVersion_To_Purchased() //Also hide the Purchase button UIView.animate(withDuration: 1.0, animations: { [weak self] in self?.purchaseButton.alpha = 0 }) { [weak self] (success) in self?.selector_Top_Constraint.constant = 30 } case .failed: if let error = transaction.error { let errorDescription = error.localizedDescription print("Transaction failed due to error: \(errorDescription)") } case .restored: SKPaymentQueue.default().finishTransaction(transaction) //print("Transaction Complete") // Hide the restore button navigationItem.setRightBarButton(nil, animated: true) // Set the BaseVerion in the Db to true IAPHandler.set_BaseVersion_To_Purchased() // Also hide the Purchase button UIView.animate(withDuration: 1.0, animations: { [weak self] in self?.purchaseButton.alpha = 0 }) { [weak self] (success) in self?.selector_Top_Constraint.constant = 30 } case .deferred: //print("Purchase Deferred") break @unknown default: if let error = transaction.error { let errorDescription = error.localizedDescription print("Transaction failed due to error: \(errorDescription)") } break } } } @IBAction func purchaseButtonTapped(_ sender: UIButton) { let theAlert = UIAlertController.init(title: K.Titles.pleaseChoose, message: nil, preferredStyle: .actionSheet) let theCancleAction = UIAlertAction(title: K.Titles.cancel, style: .cancel) let thePurchaseAction = UIAlertAction(title: K.DefaultList_Buttons.purchase_BaseVersion_Btn, style: .default) { [weak self] (action2) in if SKPaymentQueue.canMakePayments() { // User can make payments let paymentRequest = SKMutablePayment() paymentRequest.productIdentifier = self!.base_Product_ID SKPaymentQueue.default().add(self!) SKPaymentQueue.default().add(paymentRequest) } else { // User cannot make payments print("User cannot make payments") } } theAlert.addAction(thePurchaseAction) theAlert.addAction(theCancleAction) theAlert.setValue(NSAttributedString(string: theAlert.title ?? "", attributes: [.font : UIFont.systemFont(ofSize: (gDefaultTextSize - 2), weight: UIFont.Weight.semibold)]), forKey: "attributedTitle") let popOver = theAlert.popoverPresentationController popOver?.sourceView = sender popOver?.sourceRect = sender.bounds popOver?.permittedArrowDirections = .any present(theAlert, animated: true) }
5
3
5.1k
Nov ’23
Problem with presentCodeRedemptionSheet() not displaying keyboard
Hello, I have a question regarding the presentCodeRedemptionSheet() function. I'm implementing an offer code input feature in a Unity app and currently testing it on TestFlight. However, when I tap the text field, the keyboard doesn't appear as shown in the attached image. If I flip the iPhone, the keyboard then shows up. Is this phenomenon specific to TestFlight or Sandbox? I am testing this issue on the following two environments, and the problem occurs in both cases. The iPhones are models sold in Japan. iPhone 11 Pro, iOS 16.4.1 iPhone 12 Pro, iOS 15.7.1 Any help or suggestions would be greatly appreciated. Thank you!
1
1
526
Oct ’23
Auto-Renew of Subscription stops after 12 renews, how to allow more autorenew in TestFlight?
I'm trying to test my IAP subscriptions. Testing in the Sandbox environment has been kind of a nightmare, because it sometimes returns out of date information on the phone and the server events are either never delivered or delivered very late, etc. It makes testing nigh impossible. So I'm trying to test via TestFlight, because that is, at least, working in the production environment and delivers events more quickly and seems to return the correct data when I query the StoreKit2 APIs. However, it's using my REAL Apple Id for the purchases (no charges though), and after it has auto-renewed my subscription 12 times... then it NEVER will auto-renew ever again (as far as I can tell). Even if I resubscribe, the subscription immediately dies at the end of that period. It's making testing impossible again... and I can't find a way to clear TestFlight purchases similar to the way I can clear purchases for Sandbox users. At this point, I have -no- confidence that my Apple IAP Subscriptions are going to work correctly and I don't seem to have a way that I can test them properly in order to gain that confidence. Is there any way to clear purchases in TestFlight or any way to allow more than 12 renewals in TestFlight?
2
1
1.4k
Oct ’23