Mac OS 15.4 breaks receipt Validation

I'm using Swift to verify receipts in myObjective C Mac Application using the following code:

@objc
class MyAppTransaction: NSObject{
@objc
class func checkReceipt() async -> String {
do {
let verificationResult = try await AppTransaction.shared
switch verificationResult {
case .verified(_):
return "VERIFIED"
case .unverified(_, _):
return "NO RECEIPT"
}
} catch {
return "ERROR" //(StoreKit.StoreKitError) unknown
}
}
}

Starting today with my upgrade to Sequoia 15.4 and XCode 16.3 receipt validation is broken. The function is going to the catch and returning "ERROR" I can't set a break point in the do {} but if I set one at the return "ERROR", in the debugger error = (StoreKit.StoreKitError) unknown. the Compiler logs an error:Failed to parse AppTransaction: missingValue(for: [StoreKit.AppTransaction.Key.appTransactionID], expected: StoreKit.BackingValue). Reading the developer documentation I can't find anything about these struct members.

I tried to use refresh() to get a receipt like I used to with exit(173) but the compiler says refresh () not found.

This is extremely troubling because I can't debug my receipt validation code and I don't know if this will happen to my users.

Do I just have to trust Apple that my users will have an application with a receipt attached?

What can I do?

Just to make sure that exit(173) is no longer working I added this code before I called my function verifyWithStoreKit();

void getReceiptData(void)
{
NSError *theError = nil;
NSData *receiptData;
NSURL *appStoreReceiptURL;
NSBundle *mainBundle = [NSBundle mainBundle];
appStoreReceiptURL = [mainBundle appStoreReceiptURL];
if((receiptData = [NSData dataWithContentsOfURL: appStoreReceiptURL options: NSDataReadingMappedAlways error: &theError]) == nil)
exit(173);
}

The first time you call exit(173), XCode will post an alert saying that exit(173) is no longer available but it actually WILL get a receipt. But then verification will fail to validate the receipt.

This is happening with a built version of the app that worked fine for a long time as well because it had a receipt. This is worrying because this could happen to my users.

Is there now no way to get a receipt to use while testing your application?

Accepted Answer

I can get a receipt by putting code like this:

let verificationResult = AppTransaction.refresh
//do{}catch{}

in the checkReceipt() function berore the do{}catch{}

Then the program will run, verifying the refreshed receipt. This is OK for developing the application but you would want to delete this before building the app for submission to the App Store. Also it's possible that only doing this once during the development wouls leave a valid receipt in your build. I'll check this out.

I spoke too soon. Refreshing the AppTransaction won't load a valid receipt.

According to the Developer Documentation:

"Important

Calling refresh() displays a system prompt that asks users to authenticate with their App Store credentials. Call this function only in response to an explicit user action, like tapping or clicking a button."

Calling let verificationResult = await AppTransaction.refresh

isn't doing this or refreshing the AppTransaction.

XCode error: Failed to parse AppTransaction: missingValue(for: [StoreKit.AppTransaction.Key.appTransactionID], expected: StoreKit.BackingValue)(

In the Developer Documentation for AppTransactionID the definition includes:

@backDeployed(before: iOS 18.4, macOS 15.4, tvOS 18.4, watchOS 11.4, visionOS 2.4))

So Apple DID change something in how it verifies receipts with OS 15.4.

Is there any way to verify that my verification code will parse receipts on 15.4?

Searching for @backDeployed in the documentation of course I can find nothing.

And naturally Searching the Developer Documentation for AppTransaction.BackingValue yields no results.

I decided to use a different approach:

@objc
class MyAppTransaction: NSObject{
@objc
class func checkReceipt() async -> String {
var appTransaction: VerificationResult<AppTransaction>?
do {
appTransaction = try await AppTransaction.shared
}
catch {
appTransaction = try? await AppTransaction.refresh()
}
do {
switch appTransaction {
case .verified(_):
return "VERIFIED"
case .unverified(_, _):
return "NO RECEIPT"
default: return "ERROR"
}
}
}
}

When my program executes the refresh() method it prompts me for my Sandbox user name and password, using two-factor authentication on my phone. Then in subsequent executions of the application it doesn't. I assume that this means that the receipt stays in the application bundle. Also I assume that refresh() won't be executed when my end users launch the application because the App Store will already have attached a valid receipt. Is this correct? Couldn't I comment out the refresh() in the final build I send to the App Store?

Hi @Tlaloc. I've been reading your posts with interest because I'm having issues with AppTransaction and 15.4. If you get chance, would you take a quick look at my post here and see if you can shed any light on it: https://developer.apple.com/forums/thread/780767

Mac OS 15.4 breaks receipt Validation
 
 
Q