Hello!
I'm working on implementing SwiftUI + StoreKit2 IAP in my app, and Xcode testing has been fantastic, now I'd like to test my app with Sandbox, where I created a Sandbox account, and it works on my physical device, I can log in, make transactions, etc. (btw, feedback: please improve the UX for Sandbox testing, it's really clumsy compared to Xcode testing with Transaction Manager)
My problem is that when I'd like to log in with my Sandbox account on the simulator (Settings -> Developer -> Sandbox Apple Account (at the bottom) -> Sign in), it doesn't work, it fails silently. I know it authenticates well, because if I type in the wrong password, I get an error popup, but with the right password, it just fails silently, hides the login sheet and the "Sign in" text remains "Sign in" (on my phone when I log in, the "Sign in" is replaced with the email address of the Sandbox account).
I know many people get errors, and they recommend to go icloud.com and acccept TOS (done that), also to download the latest iOS for the simulator (also done), these are not a problem for me, as I don't get any errors. My authentication works, but for some reason the simulator still won't log in, it fails silently, and I see no errors/reason why. Tried with different simulators (latest iOS version), same result, tried to reboot the simulator, same result.
Any idea why is this happening?
Thank you,
sendai
StoreKit Test
RSS for tagCreate and automate tests in Xcode for your app's submission and in-app purchase transactions.
Posts under StoreKit Test tag
86 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
The application is developed with Xamarin Framework and it is live now.
The customer installed the app and purchased the annual subscription.
And for some reason, they uninstall and reinstall the application on the same device.
Now user wants to restore the subscription. In the application, there is an option to Restore the subscription. But restore API not return purchase details.
But when clicking the subscription button instead of restoring the subscription, it says you subscribed to this plan".
is there any possibility of not getting VerifyRecipt even after a successful purchase?
I am currently using StoreKit2 to set up the in-app purchase subscription flow, and I have already configured the subscription products in App Connect. I created a StoreKit Configuration file in Xcode and used it in the scheme. However, after completing the purchase, the transaction.jsonRepresentation data returns a transactionId of 0. After checking the documentation, I found that I need to disable the StoreKit Configuration and enable Sandbox Testing. But after disabling the StoreKit Configuration, I can't retrieve the real product data using Product.products(for: productIds). I can confirm that the ProductId I provided is real and matches the data configured in App Connect. Could you please help me identify the issue? Thank you
As you can see in the screenshot, the verification popups that appear when making a StoreKit purchase cut off the buttons. When typing the code into the input field, the window will also flicker and stutter with random view refreshes. Is this something I can configure/change? It's not a very pleasant experience for making an in app purchase.
I have a macOS app where I am testing the in app subscription purchase using Xcode.
When I trigger the purchase using this code, a dialog is shown in the upper right of my screen, which cannot be moved and just has a "cancel" button. How can I accept the test purchase?
let result = try await product.purchase()
In developing a new MacOS app in Xcode I set up a Storekit configuration file so I could test 2 non-consumable purchases locally. I've been successfully testing them for the past couple of weeks, but suddenly yesterday I found I was unable to make purchases. I can successfully fetch products, but when I try to purchase either of them, I get the following error:
Error handling payment sheet request: Error
Domain=NSCocoaErrorDomain Code=4099 "The connection to service
created from an endpoint was invalidated from this process." UserInfo=
{NSDebugDescription=The connection to service created from an
endpoint was invalidated from this process.}
Purchase did not return a transaction: Error Domain=ASDErrorDomain
Code=5115 "Received failure in response from Xcode" UserInfo=
{NSDebugDescription=Received failure in response from Xcode,
NSUnderlyingError=0x600002d44ae0 {Error
Domain=NSCocoaErrorDomain Code=4099 "The connection to service
created from an endpoint was invalidated from this process." UserInfo=
{AMSDescription=An unknown error occurred. Please try again.,
AMSURL=http://localhost:51482/WebObjects/MZBuy.woa/wa/inAppBuy,
NSDebugDescription=The connection to service created from an
endpoint was invalidated from this process., AMSStatusCode=200,
AMSServerPayload={
"app-list" = (
);
dialog = {
cancelButtonString = Cancel;
defaultButton = Buy;
explanation = "Do you want to buy one App Registration for $2.99?\n\n[Environment: Xcode]";
initialCheckboxValue = 1;
"m-allowed" = 0;
message = "Confirm Your In-App Purchase";
okButtonAction = {
buyParams = "bid=com.airlinemates.backup&bvrs=1.4&offerName=EIBREG&quantity=1&deviceVerification=5084f98e-ab99-5846-827e-048d00d9fac3";
itemName = EIBREG;
kind = Buy;
};
okButtonString = Buy;
paymentSheetInfo = {
caseControl = true;
confirmationTitle = Pay;
countryCode = US;
currency = USD;
designVersion = 2;
displayPrice = "$2.99";
flexList = (
{
value = (
{
style = priceMain;
value = "$2.99";
},
{
style = priceSub;
value = "One-time charge";
}
);
},
{
header = "$null";
value = "For testing purposes only. You will not be charged for confirming this purchase.";
}
);
price = "2.99";
requestor = AppStore;
salableIcon = "http://localhost:53078/StoreKit/AppIcon?bid=com.airlinemates.backup";
salableIconType = app;
salableInfo = (
"App Registration %%image_0%%",
backup,
"In-App Purchase"
);
styles = (
{
bold = true;
name = priceMain;
size = large;
},
{
color = gray;
name = priceSub;
},
{
bold = true;
name = priceMainSpaceBefore;
size = large;
spacingBefore = medium;
}
);
title = {
type = text;
value = Xcode;
};
};
};
"download-queue-item-count" = 0;
dsid = 17322632127;
failureType = 5115;
jingleAction = inAppBuy;
jingleDocType = inAppSuccess;
pings = (
);
}}}}
I've Googled & can't find any reference to this specific error, or even anything that points me in a direction to find the root cause. It's very strange because I haven't made any changes to the code & I hadn't changed the configuration file prior to this error appearing. I've since deleted the configuration file & created a new one - but it's still not working. If I create a transaction in Storekit transaction manager, the app picks it up as having been purchased - so the issue is only isolated to purchases initiated from the app.
If I stop using the configuration file when I run the app, it works fine through sandbox testing the real items in App Store Connect.
I am trying to test this simulated Error.
The issue is, I can't get this to trigger through the simulatedError function.
Error will always end up as an unknown error
Example code snippet:
@available(iOS 17.0, *)
func testPurchase_InvalidQuantity() async throws {
// Arrange
testSession.clearTransactions()
testSession.resetToDefaultState()
let productID = "consumable_1"
try await testSession.setSimulatedError(.purchase(.invalidQuantity), forAPI: .purchase)
guard let product = await fetchProduct(identifier: productID) else {
XCTFail("Failed to fetch test product")
return
}
let option = Product.PurchaseOption.quantity(4)
let result = await manager.purchase(product: product, options: option)
switch result {
case .success:
XCTFail("Expected failure due to invalid quantity")
case .failure(let error):
print("Received error: \(error.localizedDescription)")
switch error {
case .purchaseError(let purchaseError):
XCTAssertEqual(purchaseError.code, StoreKitPurchaseError.invalidQuantity.code)
default:
XCTFail("Unexpected error: \(error)")
}
}
}
In the above code snippet, I have an Unexpected Error.
But if i remove try await testSession.setSimulatedError(.purchase(.invalidQuantity), forAPI: .purchase) I will receive a XCTFail in the success of my result.
So when I set the quantity to a -1, only then can I correctly receive an invalidQuantity.
Does anyone know why the try await testSession.setSimulatedError(.purchase(.invalidQuantity), forAPI: .purchase) would fail to work as directed? I have tests for all the generic errors for loadProducts API and the simulatedError works great for them
We are in the process of implementing promotional offers for auto-renewable subscriptions in our app using StoreKit 2.
For testing, we use a sandbox user alongside a new user on our platform. I can successfully purchase an Introductory Offer through the app. Once the user is eligible for a Promotional Offer (based on a previous purchase), we retrieve the Promotional Offer identifier and signature from our backend and display the offer.
After initiating the purchase and having the user enter their Sandbox password, the transaction is added to the Payment Queue. However, it fails with the following error:
Purchase failed error: invalidOfferSignature
Additionally, the error returned is:
"Purchase did not return a transaction: Error Domain=ASDServerErrorDomain Code=3903 "Unable to Purchase" UserInfo={NSLocalizedFailureReason=Unable to Purchase, client-environment-type=Sandbox, AMSServerErrorCode=3903, storefront-country-code=IND}"
We are using StoreKit 2 APIs for this process.
Has anyone encountered this issue when working with StoreKit 2, or found a solution to resolve it?
Hello all!
My application written with C++/CMake and when building have some troubles with adding test environment for StoreKit every time - it need to be done manually.
Is there any way to add StoreKit environment settings with CMake and when executed command like this $ cmake -g Xcode ... StoreKit test environment were automatically added?
When simulating a Storekit error like an invalid device verification or others of that type, should we finish a failed transaction? When I test with a storekit configuration file, all failed transactions persist after every restart. The Apple-provided sample code for storekit 2 has transactions finished only when they are successful.
Hello, I’m trying to change my business model within the app, and following Apple’s documentation guidelines HERE I created this task in the main view of the app. It seems to work perfectly in the simulator, on physical devices, and on TestFlight. However, after releasing it to production and uploading the new version to the App Store, it doesn’t work, and all users, whether new or existing, are asked to subscribe. In the console, it appears to retrieve the transactions correctly, but in production, I’m not sure how to view the console or see what it’s retrieving.
Here the sandbox receipt I obtained
AppTransaction.shared obtained: {
"applicationVersion" : "1",
"bundleId" : "com.anestesiaIB.Drugs-Infusion-Calc",
"deviceVerification" : "6M0Nnw14nSEOBVTPE\/\/EfnWSwLm7LFSlrpFEwxgH74SBHp5dSzBEm896Uvo42mwr",
"deviceVerificationNonce" : "8a8238c0-0aee-41e6-bfb0-1cfc52b70fb6",
"originalApplicationVersion" : "1.0",
"originalPurchaseDate" : 1375340400000,
"receiptCreationDate" : 1737577840917,
"receiptType" : "Sandbox",
"requestDate" : 1737577840917
}
This are the processing log while verified the receipt
New business model change: 1.7
Original versionéis components: ["1", "0"]
Major version: 1, Minor version: 0
This user is premium. Original version: 1.0
This is my task...
.task {
do {
let shared = try await AppTransaction.shared
if case .verified(let appTransaction) = shared {
let newBusinessModelVersion = (1, 7) // Representado como (major, minor)
let versionComponents = appTransaction.originalAppVersion.split(separator: ".")
if let majorVersion = versionComponents.first.flatMap({ Int($0) }),
let minorVersion = versionComponents.dropFirst().first.flatMap({ Int($0) }) {
if (majorVersion, minorVersion) < newBusinessModelVersion {
self.premiumStatus.isPremium = true
isPremium = true
} else {
let customerInfo = try await Purchases.shared.customerInfo()
self.premiumStatus.isPremium = customerInfo.entitlements["premium"]?.isActive == true
isPremium = self.premiumStatus.isPremium
}
} else {
print("Error: obteining version components")
}
} else {
print("Not verified")
}
} catch {
print("Error processing transaction: \(error.localizedDescription)")
}
}
Hello,
I'am trying to validate a new app from apple.
I have configured sandbox url and production url for server to server apple store notification.
I have created subscriptions product id, but there are no validated yet by apple.
When I upload my app to testflight, I can't test sandbox notification cause the product id are not available, on local xcode with storekit file I got my product Id but I don't receive notification.
So my question is how can I test there thing. I have to produce fake product id ? I'm lock its very complex process, I don't understand.
I tried to send my app like that for verification but the team told me to use receipt, but its deprecated and notification webhook is better for me.
What is the good order, validate my subscription product ID then test ? what is the good steps.
Apple seems to don't want validate my subscription product ID I'm lock..
Hi everyone,
I'm finally in the final steps of putting my first app online, but I'm encountering an issue during the review process. I need to fix the subscription, but the Sandbox account isn't being triggered in TestFlight.
In the logs, I see the main account being requested instead, and the TestFlight payment process isn't starting.
Does anyone know a workaround for this?
Thanks in advance for your help!
Hello, I’m trying to change the business model of my app to in-app subscriptions. My goal is to ensure that previous users who paid for the app have access to all premium content seamlessly, without even noticing any changes.
I’ve tried using RevenueCat for this, but I’m not entirely sure it’s working as expected. I would like to use RevenueCat to manage subscriptions, so I’m attempting a hybrid model. On the first launch of the updated app, the plan is to validate the app receipts, extract the originalAppVersion, and store it in a variable. If the original version is lower than the latest paid version, the isPremium variable is set to true, and this status propagates throughout the app. For users with versions equal to or higher than the latest paid version, RevenueCat will handle the subscription status—checking if a subscription is active and determining whether to display the paywall for premium features.
In a sandbox environment, it seems to work fine, but I’ve often encountered situations where the receipt doesn’t exist. I haven’t found a way to test this behavior properly in production. For example, I uploaded the app to TestFlight, but it doesn’t validate the actual transaction for a previously purchased version of the app. Correct me if I’m wrong, but it seems TestFlight doesn’t confirm whether I installed or purchased a paid version of the app.
I need to be 100% sure that users who previously paid for the app won’t face any issues with this migration. Is there any method to verify this behavior in a production-like scenario that I might not be aware of?
I’m sharing the code here to see if you can confirm that it will work as intended or suggest any necessary adjustments.
func fetchAppReceipt(completion: @escaping (Bool) -> Void) {
// Check if the receipt URL exists
guard let receiptURL = Bundle.main.appStoreReceiptURL else {
print("Receipt URL not found.")
requestReceiptRefresh(completion: completion)
return
}
// Check if the receipt file exists at the given path
if !FileManager.default.fileExists(atPath: receiptURL.path) {
print("The receipt does not exist at the specified location. Attempting to fetch a new receipt...")
requestReceiptRefresh(completion: completion)
return
}
do {
// Read the receipt data from the file
let receiptData = try Data(contentsOf: receiptURL)
let receiptString = receiptData.base64EncodedString()
print("Receipt found and encoded in base64: \(receiptString.prefix(50))...")
completion(true)
} catch {
// Handle errors while reading the receipt
print("Error reading the receipt: \(error.localizedDescription). Attempting to fetch a new receipt...")
requestReceiptRefresh(completion: completion)
}
}
func validateAppReceipt(completion: @escaping (Bool) -> Void) {
print("Starting receipt validation...")
guard let receiptURL = Bundle.main.appStoreReceiptURL else {
print("Receipt not found on the device.")
requestReceiptRefresh(completion: completion)
completion(false)
return
}
print("Receipt found at URL: \(receiptURL.absoluteString)")
do {
let receiptData = try Data(contentsOf: receiptURL, options: .alwaysMapped)
print(receiptData)
let receiptString = receiptData.base64EncodedString(options: [])
print("Receipt encoded in base64: \(receiptString.prefix(50))...")
let request = [
"receipt-data": receiptString,
"password": "c8bc9070bf174a8a8df108ef6b8d2ae3" // Shared Secret
]
print("Request prepared for Apple's validation server.")
guard let url = URL(string: "https://buy.itunes.apple.com/verifyReceipt") else {
print("Error: Invalid URL for Apple's validation server.")
completion(false)
return
}
print("Validation URL: \(url.absoluteString)")
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.httpBody = try? JSONSerialization.data(withJSONObject: request)
URLSession.shared.dataTask(with: urlRequest) { data, response, error in
if let error = error {
print("Error sending the request: \(error.localizedDescription)")
completion(false)
return
}
guard let data = data else {
print("No response received from Apple's server.")
completion(false)
return
}
print("Response received from Apple's server.")
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
print("Response JSON: \(json)")
// Verify original_application_version
if let receipt = json["receipt"] as? [String: Any],
let appVersion = receipt["original_application_version"] as? String {
print("Original application version found: \(appVersion)")
// Save the version in @AppStorage
savedOriginalVersion = appVersion
print("Original version saved in AppStorage: \(appVersion)")
if let appVersionNumber = Double(appVersion), appVersionNumber < 1.62 {
print("Original version is less than 1.62. User considered premium.")
isFirstLaunch = true
completion(true)
} else {
print("Original version is not less than 1.62. User is not premium.")
completion(false)
}
} else {
print("Could not find the original application version in the receipt.")
completion(false)
}
} else {
print("Error parsing the response JSON.")
completion(false)
}
} catch {
print("Error processing the JSON response: \(error.localizedDescription)")
completion(false)
}
}.resume()
} catch {
print("Error reading the receipt: \(error.localizedDescription)")
requestReceiptRefresh(completion: completion)
completion(false)
}
}
Some of these functions might seem redundant, but they are intended to double-check and ensure that the user is not a previous user. Is there any way to be certain that this will work when the app is downloaded from the App Store?
Thanks in advance!
Hi everyone,
I'm finally in the final steps of putting my first app online, but I'm encountering an issue during the review process. I need to fix the subscription, but the Sandbox account isn't being triggered in TestFlight.
In the logs, I see the main account being requested instead, and the TestFlight payment process isn't starting.
Does anyone know a workaround for this?
Thanks in advance for your help!
Brice
I have configured the auto-renewable subscription product through Apple Store Connect, made it available in Xcode via the .storekit configuration file, and successfully purchased it in the testing of the old version. However, during the development of the new version, it seems that I mistakenly opened the .storekit configuration file of the old version, and then one or two pop-up windows appeared (the specific content was similar to saving? I don't quite understand), after that, I found that my new version .storekit configuration file could not automatically sync the auto-renewable subscription product from Apple Store Connect. I can't understand what happened here.
我通过Apple store Connect配置好了自动续期订阅产品,将它在Xcode通过.storekit配置文件配置可用,并且在旧版本的测试中成功购买。
但是,在开发新版本的过程中,我似乎错误的打开了旧版本的.storekit配置文件,然后弹出了一个或者两个弹窗(具体内容类似保存?我不太理解),之后,我发现我的新版本.storekit配置文件无法从Apple store Connect自动同步自动续期订阅产品。
我无法理解这里发生了什么问题。
I tried to get this post into the StoreKit forum because this issue is relative to In-App Purchases.
My App has In-App Purchases, which work, no issues here.
My App has been on the App Store for a number of years, with changes along the way. Recently, I uploaded V5.1 (Lottery Snitch) for review and the reviewer found something that had eluded everyone, until now.
Since my App has In-App Purchases, of course I have Restore In-App Purchases as a User selectable function, on the menu at the top.
The reviewer reported my App as crashing when this option was selected, which was a new thing since my App has been functioning for years.
Skipping the next several communications and moving on to the most current findings..
If my App is put onto a Mac, iMac.. Where the User has never used my app before (this eliminates leftover data files), if the User then logs out of their Apple ID prior to running my app, starts my app, selects Restore In-App Purchases the User is then presented with Apple's Request to Log-In (this has nothing to do with me..not my code..it is all 100% Apple Login request). Now, completely ignore the request for login, allow my App to complete its wait period, the User can execute any task they wish. The App runs just fine. As soon as the User selects 'Cancel' on the Apple ID login pop-up screen, my App crashes.
The Apple Login request is triggered by the restoreCompletedtransactions function for the StoreKit. The crash report indicates the DispatchQueue was the code running at the time. Thing is, my code has no DispatchQueue running. When the wait-timer completes (obvious on-screen loop) my code has zero Dispatch's running. When my code called the restoreCompletedTransactions it was not inside a Dispatch of my creation.
Anyone see this before? Anyone have a suggestion how to make this stop?
FYI, go ahead and login to your Apple ID when prompted and everything completes just fine. Yes, this problem exists in the current version(V5.0) available for download on the AppStore. It would take another post just as long to explain how this slid by on Development machines, just as weird.
What to do?
(JSYK:The App does not crash during development when running inside Xcode)
I'm attempting to test an in app purchase for my app on my phone (not in a simulator, not sandbox testing). I'm getting an error that certificate check has failed.
Could this have anything to do with the SHA-1 warnings that Apple has recently mentioned?
I've tried regenerating my StoreKit file, cleaning the build, restarting XCode, resetting all of my devices purchases from the Debug > StoreKit menu, all with no luck.
Any help would be greatly appreciated.
2025-01-10 19:52:19.974564-0500 MyApp[74478:30675548] [Default] Failed to verify certificate chain due to client recoverable failure:
Error Domain=NSOSStatusErrorDomain Code=-67818 "“StoreKit Testing in Xcode” certificate is expired" UserInfo={NSLocalizedDescription=“StoreKit Testing in Xcode” certificate is expired, NSUnderlyingError=0x3027b9d40 {Error Domain=NSOSStatusErrorDomain Code=-67818 "Certificate 0 “StoreKit Testing in Xcode” has errors: Certificate is not temporally valid;" UserInfo={NSLocalizedDescription=Certificate 0 “StoreKit Testing in Xcode” has errors: Certificate is not temporally valid;}}}
2025-01-10 19:52:19.978233-0500 MyApp[74478:30675483] [Default] Failed to verify signature for Transaction, will assume invalid: failedToVerifyCertificateChain
Purchase succeeded but verification failed: Certificate Chain Invalid
Failed to purchase Premium: invalidCertificateChain
saveUnencrypted: Started saving form_info.json
saveUnencrypted: Saved form_info.json to Documents Directory in 9 ms (JSONEncoder chunk-based copy-on-write, 1 chunks) at ...
I have been struggling to test the IAP response but it is returning empty. I am now in the very beginning of one app, and I don't want to submit the contacts and banking and tax stuff that early. are these necessary for even testing IAP results locally? I think it does not make sense if I have to.
I am developing an app with support for In-App Purchases (IAP) for consumable products using StoreKit. I have defined the products in ProductList.plist and Product.storekit, but I am unable to connect them correctly to App Store Connect. Here are the details:
Products defined in ProductList.plist:
Bolet Evento Vip: com.cover.boleto.vip
Boleto Evento Básico: com.cover.boleto.basico
Configuration in Product.storekit:
The products have prices and basic configurations, but they do not seem to link properly in App Store Connect.
Steps I have taken:
Configured IAP simulation in Xcode.
Attempted to register the products in App Store Connect.
Issues I am facing:
The products are not appearing in App Store Connect after configuration.
My app cannot seem to fetch consumable products from App Store Connect.
Question:
What steps should I follow to correctly register consumable products in App Store Connect and connect the app with StoreKit for production?
Any advice or guidance would be greatly appreciated. Thank you!