Reputation: 9123
I am validating my consumable in-app purchase on the server-side.
That is, I get the receipt from the client-side via:
.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:
// Get the receipt if it's available
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:
receiptString: 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
And then the receipt string is sent to the server:
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,
// 'password': 'MY_SECRET_PASSWORD', // Not needed for Consumable IAP's
'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, the above code tries the production verifyReceipt
endpoint, and if that fails with a sandbox error (21007), it tries the sandbox endpoint. However it never tries the sandbox endpoint as a different error comes up the first try:
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?
Edit: I have been getting the same error for 3 days with constant testing, trying everything and still getting a 21002 every time. I'm quite lost.
Upvotes: 3
Views: 6054
Reputation: 478
I have the same issue and made one mistake: I forgot to select None in the Edit-scheme after syncing the package from the iTunes account.
Upvotes: 3
Reputation: 2308
First of all your receipt is definitely malformed, the 21002 status code means it is malformed. You can also check it here, https://www.revenuecat.com/apple-receipt-checker.
Your swift and js code seems to be 100% legit, so no worries about that!
What could possibly be the problem is that your receipt file is corrupt, could you please remove the app completely from your device and reinstall it?
Or try it on a different device.
Upvotes: 3
Reputation: 381
looks like you trying to verify receipt with storekit local testing environment in simulator (proposed on wwdc2020), right? i mean you getting receipt in application this way, doesn't matter if you will check this receipt with api call from your application or the some separate backend application (yep, i'm checked)
if so, it will not work
you should do all the things without this new feature, as it was on 13 and below (by creating products in appstoreconnect and so on), this way receipt verification works as it should.
p.s. i faced the same problem with testing in-app purchases in the simulator locally
Upvotes: 8