Hello,
I would like to draw your attention to the following imperfection. For validating purchases of my paid application Guru Maps Pro, I use the download id. This is a unique ID that can replace the Transaction ID for paid applications. However, with the release of the new AppTransaction API, this field is no longer present in the data. I tried parsing the receipt, but that field is absent there as well. The only way to obtain the download id is to send the receipt to the deprecated /verifyReceipt endpoint. This deprecated status concerns me, because at some point it might stop working.
Let me explain a little about why I need this. My users have a guru-account, which they can use both in the web version and on Android. When a user purchases the paid version of the application, they can access the paid features on both web and Android. This works great for in-app purchases, where there is a transaction ID, but it may soon stop working for paid applications because there is no way to determine any ID associated with the purchase. Transaction ID or Download ID – I don't mind which.
App Store Receipts
RSS for tagValidate app and in-app purchase receipts with the App Store using App Store Receipts.
Posts under App Store Receipts tag
45 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Hi all,
I am adding the following StoreKit 2 code to my app, and I don't see anything in Apple's documentation that explains the unverified case. When is that case exercised? Is it when someone has tampered with the app receipt? Or is it for more mundane things like poor network connectivity?
// Apple's docstring on `shared` states:
// If your app fails to get an AppTransaction by accessing the shared property, see refresh().
// Source: https://developer.apple.com/documentation/storekit/apptransaction/shared
var appTransaction: VerificationResult<AppTransaction>?
do {
appTransaction = try await AppTransaction.shared
} catch {
appTransaction = try? await AppTransaction.refresh()
}
guard let appTransaction = appTransaction else {
AppLogger.error("Couldn't get the app store transaction")
return false
}
switch appTransaction {
case .unverified(appTransaction, verificationError):
// For what reasons should I expect this branch to be entered in production?
return await inspectAppTransaction(appTransaction, verifiedByApple: false)
case .verified(let appTransaction):
return await inspectAppTransaction(appTransaction, verifiedByApple: true)
}
Thank you,
Lou
Hello,
I am implementing In-App Purchases in my app and want to ensure that only users whose email matches their Apple ID email (the account used on the device as apple account) can purchase the subscription.
Is it possible to retrieve the Apple ID email from the device or verify if the email matches the user's email in the app?
If this is not feasible, what is the recommended approach to differentiate users and associate subscriptions with specific users in a secure way? For instance, how do I ensure that a subscription is tied to the correct user within my app?
I understand privacy constraints, but I am trying to find the best way to match the subscription to the correct user while adhering to Apple's guidelines. Any guidance or best practices would be appreciated.
Thank you!
https://developer.apple.com/documentation/appstoreserverapi/get-v2-history-_transactionid
I would like to inquire about the detailed triggers for updating receipts in this API specification.
Recently, I was using this API with sort=DESCENDING&revoked=false to retrieve the expiration date of the most recent receipt and determine the subscription status. However, for some reason, an old receipt with an earlier expiration date appeared as the first receipt, and I would like to know the reason for this.
Can you provide information on what specific events or actions trigger the updating of receipts in this API?
Also, regarding https://developer.apple.com/documentation/appstoreserverapi/status, will statuses 3 and 4 not be returned in the response unless the billing grace period is enabled in the App Store?
-- Japanese
こちらのAPI仕様ですが、どのようなトリガーでレシートが更新されるのか詳細に伺いたいです。
先日このAPIを使用してsort=DESCENDING&revoked=falseで一番最初のレシートの有効期限を取得して課金状態か判断していたのですが、どういったわけか一番最初のレシートに古い有効期限のレシートが入ってきたので理由を知りたいです。
また、https://developer.apple.com/documentation/appstoreserverapi/status
のステータスはAppleStoreで請求猶予期間を有効化しないと3,4はレスポンスされませんか?
With the imminent suspension of SHA-1 on App Store receipts, we desperately need an objective C code sample demonstrating how to calculate the same SDH-256 hash on device to compared with the hash from the App Store receipt.
The forced migration to SHA256 for app store receipts this month mean we have to rewrite our on device receipt validation code. However there is no documentation or objC sample code on how to validate the SHA256 hash from MAS receipts. Thje documentation at
https://developer.apple.com/documentation/technotes/tn3138-handling-app-store-receipt-signing-certificate-changes/ does not give any detail on how to validate SHA256. All my 100+ hours of experimentation and trial and error attempting to create a matching SHA256 has on device have failed.
We desperately need some ObjC Sample code to validate the SHA256 hash on device. Our existing SHA1 code is still working but we expect SHA1 hashes to disappear from MAS Receipts any day now.
Tnanks for any advice !
Stripe offers variable payment structures, also known as "irregular recurring payments," which include:
Usage-based billing: Charges amounts based on usage during the billing cycle (e.g., minutes used or energy consumed).
Quantity-based billing: Charges a pre-agreed amount based on quantity (e.g., number of users in a subscription).
Is it possible to implement this type of billing in the Apple Store for apps? How would variations in amounts be handled?
To handle the upcoming App Store receipt signing certificate changes I switched my Mac app to use the new recommended APIs about a month ago at the beginning of December 2024.
The transition worked seamlessly for most users except a few ones that only needed to re-enter their Mac App Store credentials.
My app is available discounted to Educational Institutions who use the Apple School Manager system to purchase and manage app on student's devices.
Starting Jan. 7h this week, several schools contacted me that the app fails receipt validation. Reinstalling the app from scratch doesn't help.
Is this some kind of server side failure from Apple's side?
Are there special cases to consider on my side when verifying receipts for educational purchases? (I didn't find anything in the documentation)
Here is my (simple) receipt validation code:
import StoreKit
extension AppDelegate {
func validateReceipt() {
Task {
do {
let verificationResult = try await AppTransaction.shared
switch verificationResult {
case let .verified(appTransaction):
Log.i("Receipt verification succeeded.", tag: "🧾")
case let .unverified(appTransaction, verificationError):
Log.w("Receipt verification failed with error: \(verificationError.localizedDescription)", tag: "🧾")
showVerificationFailureAlert()
}
} catch {
Log.w("Failed to retrieve AppTransaction: \(error.localizedDescription)", tag: "🧾")
showVerificationFailureAlert()
}
}
}
private func refreshReceipt() {
Task {
do {
let result = try await AppTransaction.refresh()
switch result {
case let .verified(appTransaction):
Log.i("Receipt refreshed and verified successfully.", tag: "🧾")
case let .unverified(appTransaction, verificationError):
Log.w("Refreshed receipt verification failed with error: \(verificationError.localizedDescription)", tag: "🧾")
showVerificationFailureAlert()
}
} catch {
Log.w("Failed to refresh AppTransaction: \(error.localizedDescription)", tag: "🧾")
showVerificationFailureAlert()
}
}
}
private func showVerificationFailureAlert() {
DispatchQueue.main.async {
let alert = NSAlert()
alert.messageText = "Receipt Verification Failed"
alert.informativeText = "Unable to verify the app receipt."
alert.alertStyle = .critical
// Add "Retry" and "Cancel" buttons
alert.addButton(withTitle: "Retry")
alert.addButton(withTitle: "Cancel")
let response = alert.runModal()
if response == .alertFirstButtonReturn {
self.refreshReceipt()
} else {
NSApp.terminate(nil)
}
}
}
}
Hi everybody!
I'm desperately looking for help as I'm stuck with a rather fundamental problem regarding StoreKit2 - and maybe Swift Concurrency in general:
While renovating several freemium apps I'd like to move from local receipt validation with Receigen / OpenSSL to StoreKit2. These apps are using a dedicated "StoreManager" class which is encapsulating all App Store related operations like fetching products, performing purchases and listening on updates. For this purpose the StoreManager holds an array property with IDs of all purchased products, which is checked when a user invokes a premium function. This array can have various states during the app's life cycle:
Immediately after app launch (before the receipt / entitlements are checked) the array is empty
After checking the receipt the array holds all (locally registered) purchases
Later on it might change if an "Ask to Buy" purchase was approved or a purchase was performed
It is important that the array is instantly used in other (Objective-C) classes to reflect the "point in time" state of purchased products - basically acting like a cache: No async calls, completion handler, notification observer etc.
When moving to StoreKit2 the same logic applies, but the relevant API calls are (of course) in asynchronous functions: Transaction.updates triggers Transaction.currentEntitlements, which needs to update the array property. But Xcode 16 is raising a strict error because of potential data races when accessing the instance variable from an asynchronous function / actor.
What is the way to propagate IDs of purchased products app-wide without requiring every calling function as asynchronous? I'm sure I'm missing a general point with Swift Concurrency: Every example I found was working with call-backs / await, and although this talk of WWDC 2021 is addressing "protecting mutable states" I couldn't apply its outcomes to my problem. What am I missing?
Problem Description:
The transaction ID 260002098039215 exists in the order lookup but is not retrievable via the history or subscription APIs. This inconsistency is causing difficulties in verifying the transaction.
Steps to Reproduce:
Lookup Order Details
Using the Apple Store API:
https://api.storekit.itunes.apple.com/inApps/v1/lookup/{orderId}
I queried the order details with the customer order ID: MNDBHSMSX8.
This successfully returned transaction details, including:
"transactionId": "260002098039215"
Query Transaction History
Next, I used the transaction ID (260002098039215) with the API:
https://api.storekit.itunes.apple.com/inApps/v1/history/{transactionId}
However, this query failed to return any transaction data.
Query Subscription History
As a workaround, I used the original
transaction ID (260000951614623) with the API:
https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/{originalTransactionId}
This successfully returned subscription history. However, the transaction ID 260002098039215 is missing from the subscription history response.
Request for Help:
Has anyone encountered a similar issue with missing transaction records?
Could there be an explanation for why 260002098039215 is not included in the history or subscription results?
Any guidance or insight would be greatly appreciated.
This question is about In-App Purchase.
This is an inquiry from one of our customers.
We have set up a free trial. This is your first time using the service, but you have stated that you have been charged.
Document.
https://developer.apple.com/documentation/appstorereceipts/is_trial_period
「You can use this value to determine whether the specific record is in a subscription trial period. If a previous subscription period in the receipt has the value "true" for either the is_trial_period or is_in_intro_offer_period keys, the user is not eligible for a free trial or introductory price within that subscription group.」
Our expectation is that is_trial_period is true.
Receipt is not contain is_trial_period : true or is_in_intro_offer_period : true.
Only one case has occurred. Other customers are no problem.
I have several ObjC based apps in the App Store and used to validate the receipt file inside the app in my code, and then reject it with exit(173) if it's invalid, which did trigger macOS to update the receipt if possible.
This isn't working any more in recent macOS versions, where the user is instead just told that the app is damaged, and they need to re-install it manually. Which sucks.
So I wanted to update my code. I read about SKReceiptRefreshRequest, which is supposed to re-download and install the receipt file, if I understand it correctly.
I implemented the code but now have trouble verifying that it works as intended, and does this in a user friendly way.
I found in my tests that macOS now caches the receipt in ~/Library/Caches/com.apple.appstoreagent/fsCachedData and then hardlinks the file into the app.
BTW: Sadly, this also requires that the app is located on the startup volume or the system will refuse to install the receipt, which wasn't a requirement in past times.
Now, if the receipt is already present in the cache folder, then my code works - the receipt gets re-linked.
But what if the cached receipt isn't there, yet? Such as that the user had copied the app from another Mac over to a freshly installed Mac? In the past, when the user then launched the app on the new Mac, he'd be prompted to login to the MAS and if that worked, the receipt would get installed and the app launched.
Basically, the question is: What if the receipt validation fails in my app and I request a new receipt, but the user has not yet logged into MAS (e.g. new computer)?
To simulate this, I logging out of the MAS and TestFlight, deleting all copies of the app and then run the app that I had copied from another Mac where it was authorized with a valid receipt for that device.
If I do this with the old version that uses exit(173), I get these two messages in macOS 15.2:
The second one is especially terrible because it shows the translocated path, which the average user surely get quite confused, and then maybe even search in vain for the app in there and get frustrated. But that's out of my hands. Sigh.
Now, that was proving that the old method with exit(173) isn't working any more and needs to be changed in my apps.
Since I'm still developing (testing) this new behavior, the app is therefore not in the MAS yet - the only way for me to test this is to use TestFlight. However, running a Testflight app copied from another Mac leads to this error:
That is not helpful in simulating what would happen if this app was released in the MAS. This won't let me find out what happens if my app is run on a Mac where the receipt fails and I ask it to load it via SKReceiptRefreshRequest and if the user is NOT yet logged into the MAS account for this purchased app of his/hers.
That leaves only one option: Release the app with untested code and hope for the best.
Contrary to this new behavior, the old method did let me test this easily because I would just use the special App Store tester account with the MAS app, i.e. the built MAS app would, when I launched it locally, request for a login and I'd provide my tester's account. But this isn't available any more, apparently.
What a mess.
Hi,
My app has been on the store for a few months now with a few updates.
Now I plan a major update that will affect the structure of core data itself (key attribute replaced by relationship). I see no other way than asking the users to delete and reinstall the app to reset all data and start it as new.
In a previous update I provided users the possibility to backup their data in a file so they can restore it with the future release.
Is there a better way than using the "What's new in this version" section to pass the message to the users ?
Hello everyone,
I want to set up subscriptions but I'm having problems testing them.
Indeed, when I use the storekit file to view the subscriptions it works, I see the subscriptions and I can subscribe in XCODE mode.
However, I'd like to go further and test with server notifications. So I removed the storekit file from the scheme, but I can't retrieve the products.
The application doesn't retrieve any products.. I've been stuck on it for 2 weeks... I checked the contracts and put in all the information last Friday and everything is validated.
I tested it in testflight mode, and it doesn't recognize any products!
Just so you know, I use the original API for maximum compatibility.
Dear Reviewers/Apple Team/Community,
We have trying to submit our app continuously for review and being rejected continuously by Apple. Our app works perfectly fine in TestFlight using the same ID apple is using. We have also provided recorded video of the testing in TestFlight to Apple reviewer. However, despite our requests below things are not done:
Before testing the in-app purchase subscription are not being approved, despite us requesting the same every time during submissions.
At times we are getting snapshots under error category, where everything is correct even from Apple testing.
We have no other way but to explain, give snapshots, give videos to Apple to help them understand how to test the app.
We have requested the reviewers multiple times for a call, but we got a call only 1 time.
We are not sure what is the way we can get to talk with the reviewer and understand from them the issue they are facing.
Can anyone please help us out? Below is one example, it was given it us under error category, where even with apple standard, the purchase is successful.
Can someone help us out plz.
I have some questions regarding the specifications of the receipt information that can be obtained from https://developer.apple.com/documentation/appstoreserverapi/get_transaction_info.
When a subscription is newly purchased, it is expected that the purchaseDate should reflect the time of purchase and should not be later than this time, such as an hour after the purchase. Is this understanding correct?
We observed a phenomenon where, when a user purchased a new subscription between 17:30 and 20:00 JST on November 3, 2024, the purchaseDate in the received receipt was delayed by one hour compared to the actual purchase time. Is this a specification or an issue?
When validating the receipt for a newly purchased subscription, if the purchaseDate reflects a time later than when the purchase was made, should I regard the user as having subscription rights at the time of validation?
I have 6 Mac App Store apps. They're all upfront paid, with no IAP, and they've all used the same on-device Mac App Store receipt validation code for years, which returns 173 in main() if there's not a valid receipt. Incidentally, the apps are entirely Objective-C.
I've just learned that if I compile an app with Xcode 16 and the macOS 15 SDK, I get the alert "exit(173) Not Available" when the app returns 173 on macOS 15 Sequoia. The rest of the alert text says, "The exit(173) API is no longer available. You can use Transaction.all or AppTransaction.shared to verify in-app purchases instead."
I have several questions:
Why was this done?
Where is this behavior change documented?
What are my options, given the above description of my apps?
The application has changed from paid purchase to free use. We need to obtain the previous player's purchase records to unlock the paid content.
//Here is the code for the client to obtain the player's payment information:
NSURL * receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
if ([[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]){
NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
NSString *receiptUrlString = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
NSLog(@"requestAppStoreReceipt receiptUrlString = %@", receiptUrlString);
} else {
// 如果凭证为空,则再发一次凭证请求
SKReceiptRefreshRequest *refreshReceiptRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:@{}];
refreshReceiptRequest.delegate = self;
[refreshReceiptRequest start];
NSLog(@"requestAppStoreReceipt 如果凭证为空,则再发一次凭证请求!");
}
//The following is the server-side parsing code:
Path filePath = Path.of("SubscriptionKey_ABCDEFGHIJ.p8");
String encodedKey = Files.readString(filePath);
Environment environment = Environment.SANDBOX;
AppStoreServerAPIClient client = new AppStoreServerAPIClient(encodedKey, keyId, issuerId, bundleId, environment);
String appReceipt = "MIIcs...";
ReceiptUtility receiptUtil = new ReceiptUtility();
String transactionId = receiptUtil.extractTransactionIdFromTransactionReceipt(transactionReceipt);
System.out.println(transactionId);
if (transactionId != null) {
long now = System.currentTimeMillis();
TransactionHistoryRequest request = new TransactionHistoryRequest()
.sort(TransactionHistoryRequest.Order.DESCENDING)
.revoked(false)
.productTypes(List.of(TransactionHistoryRequest.ProductType.CONSUMABLE));
HistoryResponse response = null;
List<String> transactions = new LinkedList<>();
do {
String revision = response != null ? response.getRevision() : null;
response = client.getTransactionHistory(transactionId, revision, request, GetTransactionHistoryVersion.V2);
transactions.addAll(response.getSignedTransactions());
} while (response.getHasMore());
Set<InputStream> rootCAs = Set.of(
new FileInputStream("AppleComputerRootCertificate.cer"),
new FileInputStream("AppleIncRootCertificate.cer"),
new FileInputStream("AppleRootCA-G2.cer"),
new FileInputStream("AppleRootCA-G3.cer")
);
Long appAppleId = 1234567899L; // appAppleId must be provided for the Production environment
System.out.println(transactions.size());
SignedDataVerifier signedPayloadVerifier = new SignedDataVerifier(rootCAs, bundleId, appAppleId, environment, true);
for (String notificationPayload : transactions) {
try {
AppTransaction payload = signedPayloadVerifier.verifyAndDecodeAppTransaction(notificationPayload);
System.out.println(payload);
} catch (VerificationException e) {
e.printStackTrace();
}
}
}
//Return result analysis:
JWSTransactionDecodedPayload{originalTransactionId='2000000641683476', transactionId='2000000641683476', webOrderLineItemId='null', bundleId='', productId='', subscriptionGroupIdentifier='null', purchaseDate=1719566962000, originalPurchaseDate=1719566962000, expiresDate=null, quantity=1, type='Consumable', appAccountToken=null, inAppOwnershipType='PURCHASED', signedDate=1728885967093, revocationReason=null, revocationDate=null, isUpgraded=null, offerType=null, offerIdentifier='null', environment='Sandbox', storefront='CHN', storefrontId='143465', transactionReason='PURCHASE', price=6000, currency='CNY', offerDiscountType='null', unknownFields=null}
We have develop according to the following document on our end:
https://developer.apple.com/documentation/foundation/nsbundle/1407276-appstorereceipturl#4098404
https://developer.apple.com/documentation/appstoreserverapi/get_transaction_info/
https://developer.apple.com/documentation/appstoreserverapi/data_types
We would like to know if the solutions in the document can be used to solve the problems we encountered?
Is there a problem with our method of parsing bills that prevents us from obtaining the necessary information?
Hello! Since October 2, we have observed a problem with the renewal of subscriptions. We have not changed anything, but the receipts no longer have the information whether a valid subscription exists after the subscription was actually automatically extended. The subscription status can then be queried correctly via verify Receipt. Has anything been changed to the receipts from Apple?
PS: we know that StoreKit1 is deprecated and we should switch to StoreKit2 asap.
Hello,
I have a customer who keeps getting an "app is damaged" error for a freshly downloaded app from the Mac App Store.
The logs show the following lines:
standard 12:58:40.390872+0200 storeuid Receipt Validation (at.EternalStorms.Yoink)
Signature Check: PASS
Bundle ID Check: PASS
Bundle Version Check: PASS
GUID Check: PASS
Expiration Check: PASS
standard 12:58:40.391649+0200 storelegacy StoreLegacy: Failed to perform in-line receipt renewal for application at path /Applications/Yoink.app : '(null)'
The Mac in question is running macOS 12 Monterey - curiously, the customer has another Mac with that same system version and there it works just fine.
What can be done to make this work again?
Thank you,
– Matthias
Hello,
I'd like to find out if macOS Sequoia's MAC Address randomization affects the data (specifically, MAC addresses) we receive from I/O Kit.
For context, I'd like to find out if it affects my Mac App Store receipt validation code in any way.
Thank you,
– Matthias