Validating receipt for iOS in-app purchase always returns error 21002

I am validating my consumable in-app purchase on the server-side.

That is, I get the receipt from the client-side via:

Code Block
.onChange(of: self.storeObserver.paymentStatus) { status in
switch status {
case .purchasing:
print("Payment status: purchasing")
case .failed:
self.creatingGame = false
print("Payment status: failed")
case .deferred:
print("Payment status: deferred")
case .restored:
print("Payment status: restored")
case .purchased:
if Bundle.main.appStoreReceiptURL == nil {
print("appStoreReceiptURL is nil")
}
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
do {
let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
let receiptString = receiptData.base64EncodedString(options: [])
print("receiptString: \(receiptString)")
// Read receiptData
createGame(receiptString: receiptString)
}
catch { print("Couldn't read receipt data with error: " + error.localizedDescription) }
}
print("Payment status: purchased")
default:
print("Payment status: default")
}
}
private func createGame(receiptString: String){
let data: [String:Any?] = [
"gameName": self.gameName,
"receipt": receiptString
]
callFunction(name: "validateReceipt", data: data){ result, err in
}


print("receiptString: \(receiptString)") prints the following:
Code Block
Receipt data: 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

And then the receipt string is sent to the server:
Code Block
exports.validateReceipt = functions.https.onCall(async (data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError('permission-denied', 'The function must be called while authenticated.');
}
if (!data.receipt) {
throw new functions.https.HttpsError('permission-denied', 'receipt is required');
}
// Now we fetch the receipt from Apple
let body = {
'receipt-data': data.receipt,
'exclude-old-transactions': true
};
const options = {
method: 'post',
body: JSON.stringify(body),
headers: {'Content-Type': 'application/json'},
};
return validateReceiptData('https://buy.itunes.apple.com/verifyReceipt', options, data, context);
});
function validateReceiptData(url, options, data, context) {
var retries = 0
return fetch(url, options).then(result => {
return result.json();
}).then(data => {
if (data.status === 21007 && retries === 0) {
retries += 1
// Retry with sandbox URL
console.log("Try sandbox URL");
return validateReceiptData('https://sandbox.itunes.apple.com/verifyReceipt', options, data, context);
}
console.log(`data.status: ${data.status}`); // prints status code 21002
// Process the result
if (data.status !== 0) {
console.log("The status code is not 0, so the receipt is invalid"); // function returns here
return false;
}
const latestReceiptInfo = data.latest_receipt_info[0];
console.log(`Receipt data is valid: ${latestReceiptInfo}`);
if (data.type === "join"){
return joinGame(data, context)
}
else if (data.type === "create"){
return createGame(data, context)
}
return 400;
});
}

As you can see above the code tries the production verifyReceipt endpoint, and if that fails it tries the sandbox endpoint. However it never tries the sandbox endpoint as a different error comes up the first try:
Code Block
21002
The data in the receipt-data property was malformed or the service experienced a temporary issue. Try again.

I have no idea why this error occurs. I am testing in sandbox if that makes any difference.

Any idea why I keep getting this error?

Im getting the same issue. I’m assuming something is broken on apples end.
hi! looks like you trying to verify receipt with storekit local testing environment in simulator (proposed on wwdc2020), right?
if so, it will not work

you should do all the things without this new feature, as it was on 13 and below, this way receipt verification works as it should.

p.s. i faced the same problem with testing in-app purchases in the simulator locally
Hi @zyablitsev - the testing is not on a simulator. I'm using an iPhone 7 Plus device running iOS 14.4

But yes I am testing in sandbox not production
i mean you getting receipt in application this way, doesn't matter if you will check this receipt with api call from your application (simulator or real device) or the some separate backend application.

you should test by creating products in appstoreconnect and so on, not with xcode storekit configuration file for local testing

@jkrouz were you able to fix that?

I experience the same problem right now

Same thing happening with me too..it was working in the morning and now getting validation error

App Store - Receipt Verification server was in Outage since yesterday. Is seems that it has been resolved but I still failing for me.

https://developer.apple.com/system-status/

https://stackoverflow.com/a/77653695/1790643

Checkout the my answer if you facing issue in DebugMode .

Validating receipt for iOS in-app purchase always returns error 21002
 
 
Q