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)
}
}
}
}