I have implemented IAP. The purchases are successful. The refresh receipt is working fine, which then calls the requestDidFinish(_ request: SKRequest) delegate. I'm fetching the receipt url through 'Bundle.main.appStoreReceiptURL'. When I convert the receipt data in base64 string and send it to app store's sandbox api and try to validate the receipt, it fails giving status code : 21002.
IAP receipt validation fails with status code: 21002
For troubleshooting steps, see TN3122: Receipt validation with the App Store fails with a non-zero error code.
I was trying with the mentioned solutions. But every time it gives me 21002 code for receipt validation. even with curl command same output. This is my code -
func refreshReceipt() {
ReceiptRefresher.shared.refresh { result in
switch result {
case .success(let receiptData):
let receiptString = receiptData.base64EncodedString(options: .endLineWithCarriageReturn)
self.validateReceipt(receiptString: receiptString)
case .failure(let error):
print(" Receipt refresh failed: \(error)")
}
}
}
func validateReceipt(receiptString: String) {
let requestDict: [String: Any] = [
"receipt-data": receiptString,
"password": "8fbc59b791104907a3ad418597854d50"
]
let requestData = try! JSONSerialization.data(withJSONObject: requestDict, options: [])
var request = URLRequest(url: URL(string: "https://sandbox.itunes.apple.com/verifyReceipt")!)
request.httpMethod = "POST"
request.httpBody = requestData
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else { return }
let json = try? JSONSerialization.jsonObject(with: data, options: [])
print(json!)
}.resume()
}
class ReceiptRefresher: NSObject, SKRequestDelegate { static let shared = ReceiptRefresher()
var completion: ((Result<Data, Error>) -> Void)?
func refresh(completion: @escaping (Result<Data, Error>) -> Void) {
self.completion = completion
let request = SKReceiptRefreshRequest()
request.delegate = self
request.start()
}
func requestDidFinish(_ request: SKRequest) {
guard let url = Bundle.main.appStoreReceiptURL else {
print("❌ No receipt URL")
completion?(.failure(NSError(domain: "NoReceiptURL", code: 1)))
return
}
print("📍 Receipt URL: \(url.path)")
do {
let data = try Data(contentsOf: url, options: .alwaysMapped)
print("✅ Receipt fetched. Size: \(data.count) bytes")
print("Raw receipt data (hex): \(data as NSData)")
completion?(.success(data))
} catch {
print("❌ Failed to read receipt: \(error.localizedDescription)")
completion?(.failure(error))
}
}
func request(_ request: SKRequest, didFailWithError error: Error) {
completion?(.failure(error))
}
}