StoreKit Test

RSS for tag

Create and automate tests in Xcode for your app's submission and in-app purchase transactions.

StoreKit Test Documentation

Posts under StoreKit Test tag

67 Posts
Sort by:
Post not yet marked as solved
0 Replies
364 Views
We are doing a Swift Package to manage our iap with storekit 2. This packages also contains the unit tests for the code. (as well as all the required files .storekit, certificate, ...) when calling SKTestSession.buyProduct(identifier: options:) we catch an error : Error Domain=SKErrorDomain Code=0 "(null)" Note when trying exactly the same code but in a project instead of a package that works perfectly. Any idea what could it be? The code is really simple : var testSession: SKTestSession! override func setUpWithError() throws { testSession = try SKTestSession(configurationFileNamed: "Products") } func testExample() async throws { do { let productID = "nonConsumable.crystal.tier1" try await testSession.buyProduct(identifier: productID) } catch { XCTFail("An error occured during purchase : \(error)") } } }
Posted
by
Post not yet marked as solved
1 Replies
364 Views
I'm currently testing subscriptions in Sandbox. In AppstoreConnect, I set a grace period of 3 days. I subscribed for a service which expired and now it's inBillingRetryPeriod state. I thought it had to do with my payment method. After updating my payment method, it still remains in that state. I checked Status.RenewalInfo's gracePeriodExpiration and expirationReason values but both produced nil. How do I exit the inBillingRetry state? I'm new to in-app purchases. Thanks. Here's the relevant code that updates subscription status: @MainActor func updateSubscriptionStatus() async { do { guard let product = storeManager.renewables.first, let statuses = try await product.subscription?.status else {return} var highestProduct: Product? = nil var highestStatus: Product.SubscriptionInfo.Status? = nil for status in statuses { switch status.state { case .expired, .revoked: continue default: let verifiedRenewalInfo = try storeManager.checkVerified(status.renewalInfo) //Find the first subscription in the store that matches id on the `status.renewalInfo` guard let newSubscription = storeManager.renewables.first(where: {$0.id == verifiedRenewalInfo.autoRenewPreference}) else { continue } guard let currentProduct = highestProduct else { highestProduct = newSubscription highestStatus = status // next status continue } let currentProductTier = storeManager.tierDuration(for: currentProduct.id) let newTier = storeManager.tierDuration(for: newSubscription.id) if newTier > currentProductTier { //updated product and status highestProduct = newSubscription highestStatus = status } } } currentSubscription = highestProduct // currentSubscription is an @State status = highestStatus // status is an @State if let mySubcriptionStatus = status, case .verified(let renewalInfo) = highestStatus?.renewalInfo { print(mySubcriptionStatus.state) // StoreKit.Product.SubscriptionInfo.RenewalState(rawValue: 3))-- inBillingRetry. print(renewalInfo.expirationReason) // nil print(renewalInfo.gracePeriodExpirationDate) // nil } } catch { print(error) } }
Posted
by
Post not yet marked as solved
0 Replies
344 Views
I am really frustrated with storekit2, is it me or is it an Apple bug? the subscription doesn't exist LITERALLY and the code still append a subscription to my currentSubscriptions!! Unfortunatelly I cannot attach an image but the susbcrioption doesn't exist in the storekit debug window but the code still append a valid subscription! https://stackoverflow.com/questions/77783897/storekit2-subscription-is-not-existant-and-still-append-subscription-bug // update the customers products @MainActor func updateCustomerProductStatus() async { var purchasedSubs: [Product] = [] var purchasedIAP: [Product] = [] //iterate through all the user's purchased products for await result in Transaction.currentEntitlements { do { //again check if transaction is verified let transaction = try checkVerified(result) //Check the `productType` of the transaction and get the corresponding product from the store. switch transaction.productType { case .consumable: if let iap = iaps.first(where: { $0.id == transaction.productID }) { purchasedIAP.append(iap) } case .autoRenewable: if let subscription = subscriptions.first(where: { $0.id == transaction.productID }) { //SUBSCRIPTION DOESN'T EXIST AND STILL GETS APPENDED!! purchasedSubs.append(subscription) } default: break } } catch { //storekit has a transaction that fails verification, don't delvier content to the user print("Transaction failed verification") } //finally assign the purchased products self.purchasedIAPs = purchasedIAP self.purchasedSubscriptions = purchasedSubs } }
Posted
by
Post not yet marked as solved
1 Replies
397 Views
I have implemented Store Kit for my Swift UI App. I defined all products in app store connect (auto-renewables & non-renewables). I tested everything in Xcode and it seems to run fine. However i want to test it in Sandbox to be able to check the server side dependencies. After creating Sandbox users and logging in to those accounts on my physical device, i am still only able to do payments in Xcode ("[Environment: Xcode]"). I added the In-App Purchase Capability to my project in the Signing & Capability Targets and made sure the app runs in debug mode. So according to the docs (https://developer.apple.com/documentation/storekit/in-app_purchase/testing_in-app_purchases_with_sandbox) everything seems to be set-up.
Posted
by
Post not yet marked as solved
0 Replies
376 Views
I'm currently testing subscriptions in Sandbox. In AppstoreConnect, I set a grace period of 3 days. I subscribed for a service which expired and now it's inBillingRetryPeriod state. I thought it had to do with my payment method. After updating my payment method, it still remains in that state. I am checking Status.RenewalInfo's gracePeriodExpiration and expirationReason values produce nil. How do I exit the inBillingRetry state? I'm new to in-app purchases. Thanks. Here's the relevant code: ... @MainActor func updateSubscriptionStatus() async { do { guard let product = storeManager.renewables.first, let statuses = try await product.subscription?.status else {return} var highestProduct: Product? = nil var highestStatus: Product.SubscriptionInfo.Status? = nil for status in statuses { switch status.state { case .expired, .revoked: continue default: let verifiedRenewalInfo = try storeManager.checkVerified(status.renewalInfo) //Find the first subscription in the store that matches id on the `status.renewalInfo` guard let newSubscription = storeManager.renewables.first(where: {$0.id == verifiedRenewalInfo.autoRenewPreference}) else { continue } guard let currentProduct = highestProduct else { highestProduct = newSubscription highestStatus = status // next status continue } let currentProductTier = storeManager.tierDuration(for: currentProduct.id) let newTier = storeManager.tierDuration(for: newSubscription.id) if newTier > currentProductTier { //updated product and status highestProduct = newSubscription highestStatus = status } } } currentSubscription = highestProduct // currentSubscription is an @State status = highestStatus // status is an @State if let mySubcriptionStatus = status, case .verified(let renewalInfo) = highestStatus?.renewalInfo { print(mySubcriptionStatus.state) // StoreKit.Product.SubscriptionInfo.RenewalState(rawValue: 3) -- inBillingRetry print(renewalInfo.expirationReason) // nil print(renewalInfo.gracePeriodExpirationDate) // nil } } catch { print(error) } }
Posted
by
Post not yet marked as solved
4 Replies
600 Views
Hi, I am following https://developer.apple.com/documentation/storekit/transaction/testing_refund_requests and trying to test refund requests. I am able to trigger refund requests from my app (in sandbox) using https://developer.apple.com/documentation/swiftui/view/refundrequestsheet(for:ispresented:ondismiss:) I do see that the notification URL receives a NotificationTypeV2.CONSUMPTION_REQUEST but it's not receiving NotificationTypeV2.REFUND The product for which the refund is triggered is a consumable type Is this expected behavior? How can I test REFUND without this? Thanks
Posted
by
Post not yet marked as solved
1 Replies
779 Views
I am developing my first app and having issues understanding how SubscriptionStoreView works. I used some business logic available from Apple, but I see other resources that insinuate the StoreKit views can handle all of the business logic itself, and all I need is the config file. Can anyone confirm? When I'm previewing SubscriptionStoreView, It just says, "The subscription is unavailable in the current storefront," and I cannot find what that means or what to fix.
Posted
by
Post not yet marked as solved
0 Replies
324 Views
I have a unit test case to test subscription plan change scenario, that is working until iOS 16, but the test failed starting iOS 17 and above. Test case setup: let session = try SKTestSession(configurationFileNamed: "IAPTestConfiguration") session.disableDialogs = true session.clearTransactions() // Buy a monthly subscription first try session.buyProduct(productIdentifier: "test.apple.test_mobile.1m") if let transaction = session.allTransactions().first { let id = transaction.identifier // and disable monthly subscription try session.disableAutoRenewForTransaction(identifier: id) } // buy a yearly subscription to create a subscription change scenario. try session.buyProduct(productIdentifier: "test.apple.test_mobile.1y")` Now this is the logic I have to determine that there is going to be a plan change: if let autoRenewPreference = renewalInfo.autoRenewPreference, autoRenewPreference != renewalInfo.currentProductID, renewalInfo.willAutoRenew { // means subscription change } until iOS 16, the above test setup resulted in : renewalInfo.autoRenewPreference = "test.apple.test_mobile.1y" renewalInfo.currentProductID = "test.apple.test_mobile.1m" renewalInfo.willAutoRenew = true But in iOS 17, the above setup seems to be not working because, the second time I try to buy a product seems to have no effect. // This has no effect, when there is already an active subscription, in our case "test.apple.test_mobile.1m" . try session.buyProduct(productIdentifier: "test.apple.test_mobile.1y") I expect there should be at least two transactions with my current setup, in iOS 16 I get 2 transactions when I query session.allTransactions() at the end, but in iOS 17 I only get 1 transaction, which is the first buyProduct, the last buyProduct seems to have no effect, as I do not get any callback in the SKPaymentQueue updateTransactions method for the last buyProduct call. In iOS 16, I get callbacks in SKPaymnetQueue for both buyProduct calls. I also tried to use async buyProduct in iOS 17, and updated my test case accordingly, but it resulted in the same behaviour. Please let me know if you need any other detail that could help sort this issue.
Posted
by
Post not yet marked as solved
0 Replies
304 Views
x.storekit:1:1 unexpected character ‘{’ x.storekit:11:1 unexpected character ‘"’ I get hundreds of errors similar to this when trying to build my application.
Posted
by
Post not yet marked as solved
2 Replies
579 Views
Hi, I currently have an introductory offer set up in App Store Connect, which is in the 'Ready to Submit' state. When testing with the StoreKit configuration testing for Xcode, the introductory offer appears correctly, and I can successfully make and cancel purchases. However, when testing on a device without the StoreKit configuration, using a new sandbox account, the introductory offer does not appear, though the introductory price is visible for subscription. Could you advise on how to ensure the introductory offer is displayed in this scenario? Using Storekit 2
Posted
by
Post not yet marked as solved
0 Replies
391 Views
I was trying to write unit test of ask to buy for SKDemo app. Even if I set SKTestSession.askToBuyEnabled = true, I got transaction state as .purchased after I call SKTestSession.buyProduct(identifier:). import StoreKitTest import XCTest final class SKDemoTests: XCTestCase { private var session: SKTestSession! override func setUp() async throws { session = try .init(SKTestSession(configurationFileNamed: "Products")) session.disableDialogs = true session.resetToDefaultState() session.clearTransactions() } func test() async throws { session.askToBuyEnabled = true try await session.buyProduct(identifier: "consumable.fuel.octane89") XCTAssertEqual(session.allTransactions().first!.state, .deferred) // Gets error here. The actual state I get is .purchased } } I was using Xcode 15.0.1 (15A507), Simulator iPhone 15 Pro iOS 17.0.1 (21A342) I couldn't find the problem so I'm happy to hear any solutions.
Posted
by
Post marked as solved
1 Replies
820 Views
Been testing all day and receipt loading has been working (testing in sandbox) on Mac Catalyst. I'm using StoreKit1. All of a sudden I'm getting the following error when refreshing a receipt: ** Error Domain=AMSErrorDomain Code=203 "Bag Load Failed Unable to retrieve app-receipt-create because we failed to load the bag." UserInfo={NSDebugDescription=Bag Load Failed Unable to retrieve app-receipt-create because we failed to load the bag.** Sandbox server down?
Post not yet marked as solved
1 Replies
606 Views
Dear anyone, especially for Apple Teams, we produce an issues when re-testing our app using sandbox environment. When purchasing an in-app-purchase subscription process completed, an error show up like below. <SKPaymentQueue: 0x2837755c0>: Payment completed with error: Error Domain=ASDErrorDomain Code=500 "(null)" UserInfo={NSUnderlyingError=0x283ad66d0 {Error Domain=AMSErrorDomain Code=301 "Invalid Status Code" UserInfo={NSLocalizedDescription=Invalid Status Code, AMSURL=https://sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy?REDACTED, AMSStatusCode=500, NSLocalizedFailureReason=The response has an invalid status code}}, storefront-country-code=IDN, client-environment-type=Sandbox} Please any help, thankyou.
Posted
by
Post not yet marked as solved
1 Replies
1.2k Views
Hi, We manage in-app purchases/subscriptions and their notifications in an server-to-server setup. We currently updated one of our devices to iOS 17. The sandbox subscription management is still not working, we encountered the same issue on iOS 16. When trying to access the sandbox subscription management menu, the authentication steps works correctly, but after that we get a error screen with the message: Cannot Connect. There is a retry button, but the same error message is prompted, after it is pressed We have this working on devices which have iOS 15.x versions. Are there any investigations or updates on this subject? Are there any workarounds to make this work? Thanks, David
Posted
by
Post not yet marked as solved
3 Replies
1k Views
Hello, What is the key for In-App Purchases entitlement I can add to my app.entitlements file in my project, so that I can autonomously enable the In-App Purchase capability? I have searched far a wide for this, however, it's unclear where it can be located. I know I can enable this capability manually by opening Xcode -> Selecting the "Signing & Capabilities" tab -> selecting "+ Capability" -> selecting "In-App Purchase" capability. However, this is not really an ideal solution for adding the capability to my app, especially when automated processes for building, testing, distributing via CI/CD are integrated. It would beneficial to be able to reference some documentation or resources for enabling capabilities (or any other build settings) autonomously in a project as opposed to having to manually click my way through enabling them. Looking forward to hearing back. Thanks!
Posted
by
Post not yet marked as solved
0 Replies
466 Views
Using a modified version of the following example var body: some View { NavigationSplitView { BackyardList(isSubscribed: isSubscribed, backyardLimit: passStatus.backyardLimit, onOfferSelection: showSubscriptionStore) .navigationTitle("Backyard Birds") .navigationDestination(for: Backyard.ID.self) { backyardID in if let backyard = backyards.first(where: { $0.id == backyardID }) { BackyardTabView(backyard: backyard) } } } detail: { ContentUnavailableView("Select a Backyard", systemImage: "bird", description: Text("Pick something from the list.")) } .sheet(isPresented: $showingSubscriptionStore) { SubscriptionStoreView(groupID: groupID) } .onInAppPurchaseCompletion { _, purchaseResult in guard case .success(let verificationResult) = purchaseResult, case .success(_) = verificationResult else { return } showingSubscriptionStore = false } } (from Apple's sample code demonstrating in-app purchases), I'm unable to complete a sandbox purchase on Apple Watch. I get the error in the UI Unable to Purchase App Sign in with your Apple ID from the Apple Watch app on your iPhone and printing purchaseResult outputs failure(StoreKit.StoreKitError.unknown). An Apple ID is signed into Settings on iOS, as well as the Apple Watch app. This occurs whether or not a separate sandbox Apple ID is signed into Settings under App Store. The subscription options UI appears as expected before attempting to purchase one.
Posted
by
Post not yet marked as solved
3 Replies
1.1k Views
When calling SKPaymentQueue.default().restoreCompletedTransactions() during unit testing it always fails. It calls the restoreCompletedTransactionsFailedWithError with an error message: Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={NSLocalizedDescription=The network connection was lost., NSUnderlyingError=0x600000eb2790 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={_kCFStreamErrorDomainKey=4, _kCFStreamErrorCodeKey=-4, NSErrorPeerAddressKey={length = 28, bytes = 0x1c1ef212 00000000 00000000 00000000 ... 00000001 00000000 }}}} This is/was working fine in iOS 16 and Xcode 14. Anyone else seen this issue?
Posted
by
Post not yet marked as solved
5 Replies
1.1k Views
In my Xcode 15 beta 8 setup, I'm encountering an issue with the iOS 17 simulator where StoreKit.Product.purchase() consistently throws StoreKit Error.Unknown while running XCTest. Inside XCTest, I have declared SKTestSession(configuration: ""). I'm using try await StoreKit.Product.purchase(options: []). However, it always throws StoreKit Error.Unknown. There's no such problem with the iOS 16.4 simulator, where I can retrieve the result and handle it appropriately. This issue is only present in the iOS 17 simulator. Is there any necessary workaround or fix for this? I've also included the console log output for your reference: デフォルト 10:06:45.981812+0900 storekitd AMSURLRequest: [597e_SK2] Failed to fetch client ID domains from bag. Defaulting to not including analytics cookies. error = { Error domain=AMSErrorDomain, code=204 | URL = http://localhost:XXXXX/inApps/history?REDACTED
Posted
by
Post not yet marked as solved
1 Replies
818 Views
Issue 1: When testing iOS subscriptions with StoreKit 2 in a sandbox environment on iOS 16.2 / Xcode 14.2 with a Sandbox Tester Account on a real device, I encountered a strange problem. The first 5 subscription renewals worked correctly, but after that, it didn't renew, and I received the following notification: 'notificationType': 'EXPIRED', 'subtype': 'VOLUNTARY'. According to the App Store Server documentation, this subtype indicates that the subscription expired after the user disabled subscription auto-renewal. However, I did not manually disable Auto-Renewal. Issue 2: After a subscription expires and is not renewed, attempting to re-subscribe doesn't show the confirmation dialog and returns success without any error message. The transactionId is the ID of the expired subscription. However, if I switch to a different productId plan, such as switching from a monthly payment plan (which doesn't show the dialog) to an annual payment plan (which does show the dialog), it works as expected. I found a similar case on the Apple Developer forums: https://developer.apple.com/forums/thread/723126, but the provided solutions didn't work for my situation. Hope you guys can help me out.
Posted
by