I am validating my consumable in-app purchase on the server-side.
That is, I get the receipt from the client-side via:
print("receiptString: \(receiptString)") prints the following:
And then the receipt string is sent to the server:
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:
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?
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?