Both the legacy StoreKit API and the new StoreKit 2 API return the incorrect storefront countryCode. My actual Apple ID region is Germany, and my Sandbox test user is set to France, yet the SDK consistently returns USA.
Expected Results:
The returned storefront countryCode should reflect the correct region - sandbox user region if signed in and real user region if not signed in with sandbox).
Actual Results:
Returned country code is USA with both
SKPaymentQueue.default().storefront?.countryCode
and
await Storefront.current?.countryCode.
Signing out/in, device reboot and even reset do not help, I'm stuck with USA storefront.
StoreKit
RSS for tagSupport in-app purchases and interactions with the App Store using StoreKit.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
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)
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?
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)
Hey y'all,
I'm reaching out because of an observed issue that I am experience both in sandbox and in production environments. This issue does not occur when using the Local StoreKit configurations.
For context, my app only implements auto-renewing subscriptions. I'm trying to track with my own analytics every time a successful purchase is made, whether in the app or externally through Subscription Settings. I'm seeming too many events for just one purchase.
My app is observing Transaction.updates. When I make a purchase with Product.purchase(_:), I successfully handle the purchase result. After about 10-20 seconds, I receive 2-3 new transactions in my Transaction.updates, even though I already handled and finished the Purchase result. This happens on production, where renewals are one week. This also happens in Sandbox, where at minimum renewals are every 3 minutes.
The transactions do not differ in transactionId, revocationDate, expirationDate, nor isUpgraded... so not sure why they're coming in through Transaction.updates if there are no "updates" to be processing.
For purchases made outside the app, I get the same issue. Transaction gets handled in updates several times.
Note that this is not an issue if a subscription renews. I use `Transaction.reason
I want to assume that StoreKit is a perfect API and can do no wrong (I know, a poor assumption but hear me out)... so where am I going wrong?
My current thought is a Swift concurrency issue. This is a contrived example:
// Assume Task is on MainActor
Task(priority: .background) { @MainActor in
for await result in Transaction.updates in {
// We suspend current process,
// so will we go to next item in the `for-await-in` loop?
// Because we didn't finish the first transaction, will we see it again in the updates queue?
await self.handle(result)
}
}
@MainActor
func handle(result) async {
...
await Analytics.sendEvent("purchase_success")
transaction.finish()
}
Our app offers auto-renewable subscriptions using StoreKit Original API for In-App Purchase and App Store Server Notifications V1.
Starting around 2025-03-15, we found some cases where original_transaction_id which was associated to web_order_line_item_id of already purchased subscription had changed in the receipt information of verifyReceipt response or App Store Server Notifications V1.
The detailed steps are:
Around February 2025, re-purchase from the app the same subscription product which was canceled and expired some time ago, using StoreKit Original API for In-App Purchase
Receive the following 2 notifications from App Store Server Notifications V1 almost at the same time
INITIAL_BUY
DID_CHANGE_RENEWAL_STATUS
In both notifications, latest_receipt_info contains the receipt for the re-purchased subscription period with new original_transaction_id and web_order_line_item_id
pending_renewal_info contains both new original_transaction_id and original one which was generated at first purchase, and original one has "is_in_billing_retry_period": "1"
Starting around 2025-03-15, the following happens
When we receive another "DID_CHANGE_RENEWAL_STATUS" notification from App Store Server Notifications V1, original_transaction_id which is associated to web_order_line_item_id of the re-purchased subscription period has changed back to the original one (the one which was generated at first purchase) in latest_receipt_info.
When we call verifyReceipt with the receipt obtained from appStoreReceiptURL, the response does not seem to contain new original_transaction_id which was generated at re-purchase
We have some questions regarding this original_transaction_id behavior.
When a user re-purchase the same subscription product which was canceled and expired some time ago, it seems that new original_transaction_id is generated. Is this an expected behavior?
If yes, it seems that, at some point, original_transaction_id which is associated to web_order_line_item_id of the re-purchased subscription changed back to the original original_transaction_id which had been generated at first purchase. Is this an expected behavior?
What triggers the original_transaction_id change to the original one? Is it related to some user actions or subscription status change?
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
StoreKit
App Store Server Notifications
App Store Receipts
I've been struggling to work with the Storekit framework and specifically to find the current Storefront used by the user of the app.
Context : My app needs to behave differently depending on the country of the user.
For me relying on Locale.current.region?.identifier does not seem very reliable, the user can change it really easily.
I'm trying to use the Storekit framework like so :
if let storefront = await StoreKit.Storefront.current{
return storefront.countryCode
}
As per Apple's Storekit documentation :
Use current to determine a customer's current storefront region and offer in-app products suitable for that region. You maintain your own list of product identifiers and the storefronts in which you make them available.
But I just can't find out what I need to change in my current configuration to get another country. The code keeps returning my original storefront (which is France)
I've tried login in with a sandbox user defined on another country. Changed all settings on my device to another country. Changed my Apple's account region as described here. Also tried to logout from everything.
The only thing that works is setting a local .storekit file as described here and changing the default storefront.
Is Xcode overriding the default storefront when building on debug or TestFlight? does anyone know how can I test different storefronts with sandbox users without the local storekit file ?
Thank you in advance.
Received error that does not have a corresponding StoreKit Error: Error Domain=AMSErrorDomain Code=305 "Purchase Failed Server canceled the purchase
More details:
Error Domain=AMSErrorDomain Code=305 "Purchase Failed Server canceled the purchase" UserInfo={AMSFailureReason=Server canceled the purchase, AMSURL=https://sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy?guid=00008110-000A4DC10E51401E, AMSDescription=Purchase Failed, AMSStatusCode=200, AMSServerPayload={
"cancel-purchase-batch" = 1;
customerMessage = "Unable to process your request.";
dialog = {
defaultButton = ok;
explanation = "Please try again later.\n\n[Environment: Sandbox]";
initialCheckboxValue = 1;
isFree = 1;
"m-allowed" = 0;
message = "Unable to process your request.";
okButtonString = OK;
};
failureType = "";
"m-allowed" = 0;
metrics = {
actionUrl = "sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy";
asnState = 0;
dialogId = "MZCommerce.SystemError";
eventType = dialog;
message = "Unable to process your re";
mtEventTime = "2025-07-28 12:34:22 Etc/GMT";
mtTopic = "xp_its_main";
options = (
OK
);
};
pings = (
);
}, NSDebugDescription=Purchase Failed Server canceled the purchase}
Received error that does not have a corresponding StoreKit Error: Error Domain=ASDErrorDomain Code=500 "(null)" UserInfo={client-environment-type=Sandbox, storefront-country-code=IND, NSUnderlyingError=0x1276116e0 {Error Domain=AMSErrorDomain Code=305 "Purchase Failed Server canceled the purchase" UserInfo={AMSFailureReason=Server canceled the purchase, AMSURL=https://sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy?guid=00008110-000A4DC10E51401E, AMSDescription=Purchase Failed, AMSStatusCode=200, AMSServerPayload={
"cancel-purchase-batch" = 1;
customerMessage = "Unable to process your request.";
dialog = {
defaultButton = ok;
explanation = "Please try again later.\n\n[Environment: Sandbox]";
initialCheckboxValue = 1;
isFree = 1;
"m-allowed" = 0;
message = "Unable to process your request.";
okButtonString = OK;
};
failureType = "";
"m-allowed" = 0;
metrics = {
actionUrl = "sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy";
asnState = 0;
dialogId = "MZCommerce.SystemError";
eventType = dialog;
message = "Unable to process your re";
mtEventTime = "2025-07-28 12:34:22 Etc/GMT";
mtTopic = "xp_its_main";
options = (
OK
);
};
pings = (
);
}, NSDebugDescription=Purchase Failed Server canceled the purchase}}}
Purchase did not return a transaction: Error Domain=ASDErrorDomain Code=500 "(null)" UserInfo={client-environment-type=Sandbox, storefront-country-code=IND, NSUnderlyingError=0x1276116e0 {Error Domain=AMSErrorDomain Code=305 "Purchase Failed Server canceled the purchase" UserInfo={AMSFailureReason=Server canceled the purchase, AMSURL=https://sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy?guid=00008110-000A4DC10E51401E, AMSDescription=Purchase Failed, AMSStatusCode=200, AMSServerPayload={
"cancel-purchase-batch" = 1;
customerMessage = "Unable to process your request.";
dialog = {
defaultButton = ok;
explanation = "Please try again later.\n\n[Environment: Sandbox]";
initialCheckboxValue = 1;
isFree = 1;
"m-allowed" = 0;
message = "Unable to process your request.";
okButtonString = OK;
};
failureType = "";
"m-allowed" = 0;
metrics = {
actionUrl = "sandbox.itunes.apple.com/WebObjects/MZBuy.woa/wa/inAppBuy";
asnState = 0;
dialogId = "MZCommerce.SystemError";
eventType = dialog;
message = "Unable to process your re";
mtEventTime = "2025-07-28 12:34:22 Etc/GMT";
mtTopic = "xp_its_main";
options = (
OK
);
};
pings = (
);
}, NSDebugDescription=Purchase Failed Server canceled the purchase}}}
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
Subscriptions
StoreKit Test
StoreKit
In-App Purchase
I'm implementing PurchaseIntent.intents for App Store in-app purchase promotions, following Apple's WWDC guidance. The API only works on cold launch (killed→launch), but fails on background→foreground transitions, making App Store promotions unusable.
Sample code as followed from WWDC23 video "What's new in StoreKit 2 and StoreKit Testing in Xcode".
In the StoreKitManager observable class, I have this function which is initialized in a listening task:
func listenForPurchaseIntent() -> Task<Void, Error> {
return Task { [weak self] in
for await purchase in PurchaseIntent.intents {
guard let self else { continue }
let product = purchase.product
await self.purchaseProduct(product)
}
}
}
where purchaseProduct() will perform the call to:
try await product.purchase()
ISSUE:
When the app is in background (after previously launched), and the purchase intent is initiated from Xcode Transaction Manager or using the "itms-services://?action=purchaseIntent" method, the system foregrounds my app but the purchase intent is never delivered to the waiting listener. The intent remains queued until the next cold launch (quit app and relaunch app). This could mean that if a user has installed the app, and has run the app, then tapped the promotional IAP from the App Store, the purchase intent will not show up until the next cold launch.
If the app is in quit state, then the system will foreground the app, and purchase intent is delivered correctly.
STEPS TO REPRODUCE
Launch app (listener starts in StoreKitManager.init())
Background app
Add purchase intent via Xcode Transaction Manager
Foreground app
Result: No purchase sheet appears, no intent delivered
Workaround attempts:
Using this either in a view or the main app:
func checkForPurchaseIntents() async {
for await purchaseIntent in PurchaseIntent.intents {
await storeKit.purchaseProduct(purchaseIntent.product)
}
}
Applied to .onChange(of: scenePhase) - Doesn't work, nothing happens.
Using UIApplication.willEnterForegroundNotification - Only works on the first time the app goes from background to foreground when purchase intent is sent. Doesn't work on second time or third time.
• Attempting to creating fresh listening task on each foreground - Does not work.
The question is:
How are we supposed to implement the PurchaseIntent API?
I have checked Apple sample projects like BackyardBirds, and sample projects from WWDC on StoreKit 2 but they never implemented Purchase Intent.
Hi 👋! I want to switch the business model in my app from premium to freemium and do it gracefully for existing users. Essentially, I wish to provide newly-paywalled content for free to existing paid users (people who bought the original app).
It seems clear that I should be using appTransaction's originalAppVersion property to check against purchases made in a previous version of the app, per the documentation. However, there seems to be broad confusion over whether originalAppVersion returns the version number or the build number and how to test for it. Examples of confusion can be found here, here and here.
This lack of clarity seems especially dangerous due to the difficulty in testing these values. In the sandbox originalAppVersion returns 1.0 by default, so whether you design for version number or build number, you'll always return a positive as long as your value is more than 1. There is a real risk to unknowingly either never identify previous premium users or accidentally identify everyone as premium (essentially giving away your app for free).
For example, my app's current version number is 1.4.0 and build number is 18, so 1.4.0 (18). As this is a major change, for this new update I might as well go for version number 2.0.0, and let's say I release the app with build number 5, so 2.0.0 (5). If I expect originalAppVersion to return the version number, I would match it against 2, because anything before 2.0.0 needs to be marked as premium. However, if I expect the build number, I should check against 19 and respectively bump up my build number: 5 -> 19.
In the standard version/build "v.v.v (b)" format, does originalAppVersion return app version or app build?
If it indeed does return build, and not version, I guess I'll start all of my future build numbers from 100 just in case: 2.0.0 (100). The only way I imagine I can test this is to print the value on the visual interface in a live version of the app, and ask a random user 🤷♂️.
Topic:
App & System Services
SubTopic:
StoreKit
I’m testing an auto-renewable subscription on TestFlight. Now the user can't re-purchase the same product – Apple just restores the old (expired) one, and no payment sheet appears.
How can I let the same TestFlight user re-subscribe to an expired product?
Do I have to create a new productId for every test cycle?
is there a way to make a test subscription in-app purchase expire immediately, for faster testing? it seems exceedingly complicated to test subscriptions if we have to a) wait until the next day for expiry, or b) keep on creating new apple ids to get into a fully unsubscribed state? it is still kind of madness testing this stuff, after all the years it has been available.
Hello,
For In App Purchases with a renewable subscription, does the originalTransactionId change in the following scenarios?
Case 1:
A user subscribes to a subscription A within a Subscription Group SG1.
The user then cancels it at the end of the month.
Comes back later to subscribe to the same subscription A within the same Subscription Group SG1.
Case 2:
A user subscribes to a subscription A within a Subscription Group SG1.
The user then cancels it at the end of the month.
Comes back later to subscribe to subscription B within the same Subscription Group SG1.
Hi. If the app is in landscape only and when the SKStoreProductViewController is presented, the safeArea changes to what looks like a portrait mode safe area. When the SKStoreProductViewController is dismissed, the safeArea does NOT revert back to the original values.
Is there a way to force the safeArea to "reset"? I've submitted some bug tickets through Apple Feedback but I haven't received any response about it.
The below code will pop up the SKStoreProductViewController and if you have a UIView that is constrained to the safe area, then you can visibly notice that the safe area is changed and doesn't go back.
I have tested this on iPhone 14 Pro, iPhone 15, and iPhone 16 Pro and in the Simulators. The incorrect behavior happens on those and probably more.
Thanks.
#import "ViewController.h"
#import &lt;StoreKit/StoreKit.h&gt;
@interface ViewController ()
@property (nonatomic, strong) SKStoreProductViewController *productViewController;
@end
@implementation ViewController
- (IBAction)buttonTapped:(id)sender {
self.productViewController = [[SKStoreProductViewController alloc] init];
NSDictionary *parameters = @{
@"id" : @"6443575749"
};
[self.productViewController loadProductWithParameters:parameters completionBlock:^(BOOL result, NSError * _Nullable error) {
[self presentViewController:self.productViewController animated:YES completion:^{
// presented
// The panel that is constraint to the safe area visibly shows that the safe area is no longer correct.
}];
}];
}
@end
Hi all,
I have a simple prototype subscription for a recurring monthly for $0.29 cheap!
And it works great!
But it only works great at sub time. It's stuck in the sandbox, constantly giving me "currently subscribed" status even though I’ve done a bunch of things:
Force-quit the app.
Deleted and re-installed it.
Rebooted my phone.
Signed out of media purchases.
Looked on AppStore connect to try to find anything that seems like it’d let me fix this
All efforts in vain.
I'm trying to avoid fully logging out of my iCloud account on my phone. Any other thoughts?
Topic:
App & System Services
SubTopic:
StoreKit
Hello all,
I am new to implementing payments in an app, and thus completely at sea here.
I have created a small app that I have set a one-time (non-consumable) payment for a premium version.
In the Xcode simulator (on all platforms) and on any physical test devices I have tried, the payment works as expected. I have a sandbox account and various test accounts, both dummy and actual real accounts (friends and family). Everywhere everything works perfectly fine.
Yet, when I submit for review I get a rejection with this contents:
We found that your in-app purchase products exhibited one or more bugs which create a poor user experience. Specifically, the app still failed to load the in-app purchase. Please review the details and resources below and complete the next steps.
Review device details:
Device type: iPad Air (5th generation)
OS version: iPadOS 18.6
Next Steps
When validating receipts on your server, your server needs to be able to handle a production-signed app getting its receipts from Apple’s test environment. The recommended approach is for your production server to always validate receipts against the production App Store first. If validation fails with the error code "Sandbox receipt used in production," you should validate against the test environment instead.
Additionally, note that the Account Holder must accept the Paid Apps Agreement in the Business section of App Store Connect before paid in-app purchases will function.
Resources
Learn how to set up and test in-app purchase products in >the sandbox environment.
Learn more about validating receipts with the App Store.
Steps I have done:
I have signed all agreements and all bank account details are in order. Everything in the In-app-purchases section of the AppStoreConnect in an Active state.
I have triple checked that the configuration of the in-app purchases is correct (product IDs, amounts, etc.)
I have created test accounts and tested in sandbox
What I don't understand from the reviewer's response is what receipts validation are they talking about? I have no payment servers (the whole concept of using Apple's in-app-purchases service is to not have to deal with my own payment implementation). The StoreKit documentation specifically reads:
For each transaction that represents a current purchase, your app delivers the purchased products. To validate purchases, you can verify transactions on your server, or rely on StoreKit’s verification.
So now I am confused. The reviewer's response is so vague, and so completely deprived of details that I have no idea what to do...
Does the problem concern the product purchase trigger and the that in production environment it does not trigger?
Is it that I haven't implemented a receipt validation? Do I need to? Although the documentation mentions that it can be done by StoreKit, I couldn't find anything concerning how to do it :(
Can someone give me a hand please?
Cheers,
Alex
works perfectly on android but doesn't work at all on IOS and i have used the same bundle id and product ids on both stores.
The error that i get on IOS is : "IAP initialization failed: NoProductsAvailable - No Product returned from store"
Here are the things that i've done:
Created an App ID on the apple developer portal with the correct capabilities
I have enabled the correct capabilities on the xcode project Unity Framework is embed and signed, Storekit (do not embed)
In singin and capabilities in-app purchases is there
I am using testflight to submit the app with a distribution certificate that appears to be valid
I've checked the the bundle identifier and it's the same everywhere (unity project, xcode project, App ID)
All of the products are cleared for sale and are in the status "ready to submit"
I always uninstall the old app version before testing the new one
My banking updates are still processing does this effect TestFlight IAP
Paid Apps Agreement is in Pending User Info state does this effect also
I still haven't filled out the tax forms, so I'm wondering if I need to complete them before my app's in-app purchases (IAPs) work in TestFlight.
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
Subscriptions
In-App Purchase
Apple Unity Plug-Ins
Dear Apple Support Team,
We are currently implementing auto-renewable subscriptions in our iOS app and are testing the integration using the sandbox environment.
On the iOS app side, the in-app purchase flow completes successfully and displays a "Purchase Successful" message. However, we are not receiving any server notification callbacks on our configured App Store Server Notifications (Sandbox) webhook URL.
For your reference, the webhook URL we have set in App Store Connect (Sandbox) is:
https://9c0f-182-79-123-254.ngrok-free.app/ios/webhook
Despite successfully completing a subscription purchase in the sandbox, there is no evidence that the webhook is being triggered.
We would appreciate your guidance in resolving this issue or confirming if there are any additional configurations or steps required on our end.
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
StoreKit Test
StoreKit
In-App Purchase
App Store Receipts
Strange issue with currency display in subscription products
Hi everyone,
I'm facing a strange issue in my app where I use a subscription-based in-app purchase model.
The products I created in App Store Connect are all in "Approved" status.
I've tested with both RevenueCat and StoreKit, but the result is the same.
Here are the products being loaded:
Product loaded: weekly_product_id
Display name: Weekly Pro
Description: Weekly Pro Subscription
Price: ₺229,99
Product loaded: annual_product_id
Display name: Annual Pro
Description: Annual Pro Subscription
Price: ₺1.799,99
Even though I can see the correct prices and currency (Turkish Lira) in the Xcode debug console, on my real device the currency appears as Philippine Peso, as shown in the attached screenshot.
Interestingly, in the iOS simulator, it's displayed in USD.
I've double-checked and my device's region settings are set to Turkey.
Any ideas on what could be causing this? And more importantly, how can I fix it?
Thanks in advance!
I'm developing storekitV2, my app is providing the way to refund some product, and I use method below.
func beginRefundRequest(in scene: UIWindowScene) async throws -> Transaction.RefundRequestStatus
however when i call the method, the modal view presented but the view shows error with message 'cannot connect'. when I select retry button, something done with indicator and get same result.
how can I solve this problem?
**Environment
Platform:** iOS
Distribution: TestFlight
Product type: Consumable In-App Purchase
Account used for testing: Real Apple ID (not Sandbox)
StoreKit: StoreKit 1
iOS version: iOS 17+ (also reproduced on earlier versions)
Issue Description
We are encountering an issue when testing consumable in-app purchases in a TestFlight build using a real Apple ID.
Under normal circumstances, consumable products should be purchasable repeatedly. However, in TestFlight, after a successful purchase flow, the same product may become unavailable for repurchase, and the transaction appears to be stuck, even though:
• finishTransaction: is correctly called
• The transaction state is .purchased
• No pending transactions are left in the payment queue
Once this happens, subsequent purchase attempts result in behavior similar to a non-consumable product (e.g. “already purchased” or no purchase UI shown).
I try to call Get Transaction Info from App Store Server API, and the transactionId is for a Non-consumable type product, but it is odd that there are so many different transactionId and they have a same originalTransactionId
{
"bundleId": "${bundleId}",
"environment": "Production",
"inAppOwnershipType": "PURCHASED",
"originalPurchaseDate": 1691220528000,
"originalTransactionId": "${originalTransactionId}",
"productId": "${productId}",
"purchaseDate": 1691220528000,
"quantity": 1,
"signedDate": 1692590989925,
"storefront": "USA",
"storefrontId": "143441",
"transactionId": "${originalTransactionId}",
"transactionReason": "PURCHASE",
"type": "Non-Consumable"
}
the defination of Non-Consumable is can only purchase once for same apple account. But why there would have originalTransactionId?
Topic:
App & System Services
SubTopic:
StoreKit
Tags:
In-App Purchase
App Store Receipts
App Store Server API