Verify Receipt for In-App Purchase on App Store - ios

I use the function below to verify receipts. As you can see, I used sandbox URL because I test the reciepts before I submit them to the store. What I want to learn is this: before I submit, what should I write in the storeUrl section? How can I access that URL or find it? According to my research, I need to put my server address or something, but the product that I want to submit is non-consumable so I do not have any server for that.
func verifyReciept (transaction : SKPaymentTransaction?) {
let recieptURL = Bundle.main.appStoreReceiptURL!
if let reciept = NSData(contentsOf: recieptURL){
let requestContents = ["receipt-data" : reciept.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue:0))]
do {
let requestData = try JSONSerialization.data(withJSONObject: requestContents, options: JSONSerialization.WritingOptions(rawValue : 0))
let storeURL = NSURL(string: "https:/sandbox.itunes.apple.com/verifyReceipt")
let request = NSMutableURLRequest(url: storeURL! as URL)
request.httpMethod = "Post"
request.httpBody = requestData
let session = URLSession.shared
let task = session.dataTask(with: request as URLRequest, completionHandler: { (responseData: Data?, response : URLResponse?, error : Error?) -> Void in
do {
let json = try JSONSerialization.jsonObject(with: responseData!, options: .mutableLeaves) as! NSDictionary
print(json)
if(json.object(forKey: "status") as! NSNumber) == 0 {
let receipt_dict = json["receipt"] as! NSDictionary
if let purchases = receipt_dict["in_app"] as? NSArray {
self.validatePurchaseArray(purchases: purchases)
}
if transaction != nil {
SKPaymentQueue.default().finishTransaction(transaction!)
}
}
else {
print(json.object(forKey: "status") as! NSNumber)
}
}
catch{
print(error)
}
})
task.resume()
} catch {
print(error)
}
}
else {
print("No Reciept")
}
}

You should replace that with the prod URL for verifying receipts:
"https://buy.itunes.apple.com/verifyReceipt"
The thing you are talking about involving you server is an additional measure you can take. For the server side verification, you need to send the receipt to your server and then run this same function from your server.

Related

How to Decode Apple App Attestation Statement?

I'm trying to perform the Firebase App Check validation using REST APIs since it's the only way when developing App Clips as they dont' support sockets. I'm trying to follow Firebase docs. All I'm having trouble with is the decoding of the App Attestation Statement.
So far I've been able to extract the device keyId, make Firebase send me a challenge to be sent to Apple so they can provide me an App Attest Statement using DCAppAttestService.shared.attestKey method.
Swift:
private let dcAppAttestService = DCAppAttestService.shared
private var deviceKeyId = ""
private func generateAppAttestKey() {
// The generateKey method returns an ID associated with the key. The key itself is stored in the Secure Enclave
dcAppAttestService.generateKey(completionHandler: { [self] keyId, error in
guard let keyId = keyId else {
print("key generate failed: \(String(describing: error))")
return
}
deviceKeyId = keyId
print("Key ID: \(deviceKeyId.toBase64())")
})
}
func requestAppAttestChallenge() {
guard let url = URL(string: "\(PROJECT_PREFIX_BETA):generateAppAttestChallenge?key=\(FIREBASE_API_KEY)") else {
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request) { [self] data, response, error in
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
return
}
guard let data = data, error == nil else {
return
}
let response = try? JSONDecoder().decode(AppAttestChallenge.self, from: data)
if let response = response {
let challenge = response.challenge
print("Response app check challenge: \(challenge)")
print("Response app check keyID: \(deviceKeyId)")
let hash = Data(SHA256.hash(data: Data(base64Encoded: challenge)!))
dcAppAttestService.attestKey(deviceKeyId, clientDataHash: hash, completionHandler: {attestationObj, errorAttest in
let string = String(decoding: attestationObj!, as: UTF8.self)
print("Attestation Object: \(string)")
})
}
}
task.resume()
}
I tried to send the attestation object to Firebase after converting it in a String, although I wasn't able to properly format the String. I see from Apple docs here the format of the attestation, but it isn't really a JSON so I don't know how to handle it. I was trying to send it to Firebase like this:
func exchangeAppAttestAttestation(appAttestation : String, challenge : String) {
guard let url = URL(string: "\(PROJECT_PREFIX_BETA):exchangeAppAttestAttestation?key=\(FIREBASE_API_KEY)") else {
return
}
let requestBody = ExchangeAttestChallenge(attestationStatement: appAttestation.toBase64(), challenge: challenge, keyID: deviceKeyId)
let jsonData = try! JSONEncoder().encode(requestBody)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request) {data, response, error in
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
print("Exchange App Attestation \(String(describing: response))")
return
}
guard let data = data, error == nil else {
return
}
print("Exchange App Attestation: \(data)")
}
task.resume()
}

Receipt Data Validation Info Not Coming

I have some issues with receipt validation. I want to purchased subscription expires date on show some places but do not get receipt data info.
do {
guard let receipt = Bundle.main.getReceiptData()?.base64EncodedString() else { return }
let requestDictionary = ["receipt-data": receipt]
guard JSONSerialization.isValidJSONObject(requestDictionary) else {
print("requestDictionary is not valid JSON")
return
}
let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt"
guard let validationURL = URL(string: validationURLString) else {
print("the validation url could not be created, unlikely error")
return
}
let session = URLSession(configuration: URLSessionConfiguration.default)
var request = URLRequest(url: validationURL)
request.httpMethod = "POST"
request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringCacheData
let task = session.uploadTask(with: request, from: requestData) { (data, response, error) in
if let data = data , error == nil {
do {
let appReceiptJSON = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
if let dict = appReceiptJSON as? NSDictionary {
print(dict["last_receipt_info"]!) // nil value ????
}
}catch {
print("JSON serialization failed with error: \(error.localizedDescription)")
}
} else {
print("Upload receipt data but something went wrong. Error: \(error?.localizedDescription)")
}
}
task.resume()
} catch let error as NSError {
print("JSON serialization failed. Error: \(error.localizedDescription)")
}
This function result only status code.. Why ??
The resources I have reviewed:
https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW1
https://medium.flatstack.com/auto-renewable-subscriptions-for-ios-45cb1045f4fa
https://savvyapps.com/blog/how-setup-test-auto-renewable-subscription-ios-app
Thank you for help to me..
Solved problem. Problem is not added shared secret key.
let requestDictionary = ["receipt-data": receipt, "password": "xxxxxxxxxxxxxxx"]
True way 👆🏻

Why is my app crashing on the App Store but not in development?

I suspect it's got something to do with the subscription-based in-app purchase. Everything works fine in development mode and on TestFlight, even running Ad Hoc. But when it hits the App Store, it crashes every single time.
I've checked crash logs, and the only info I'm getting is that it is a SIGTRAP.
I think it's in this code:
func checkForSubscription {
print("Checking subscription status")
guard let receiptURL = Bundle.main.appStoreReceiptURL else {
return
}
let fileManager = FileManager()
if fileManager.fileExists(atPath: receiptURL.path) {
do {
let receipt = try! Data(contentsOf: receiptURL)
let requestContents = [
"receipt-data": receipt.base64EncodedString(options: []),
"password": "secret generated from iTunes Connect"
]
let requestData = try JSONSerialization.data(withJSONObject: requestContents, options: [])
let storeURL = URL(string: "https://buy.itunes.apple.com/verifyReceipt")
let sandboxURL = URL(string: "https://sandbox.itunes.apple.com/verifyReceipt")
var request = URLRequest(url: storeURL!)
request.httpMethod = "POST"
request.httpBody = requestData
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let httpResponse = response as? HTTPURLResponse, let receivedData = data else {
print("No valid http response.")
return
}
switch (httpResponse.statusCode) {
case 200:
do {
let data = try JSONSerialization.jsonObject(with: receivedData, options: .allowFragments)
let json = JSON(data) // SwiftyJSON
let receipts = json["receipt"]["in_app"].array!
let latestReceipts = json["latest_receipt_info"].array!
var allReceipts = receipts + latestReceipts
var expiresTime: Double = 0
for receipt in allReceipts {
let expiration = receipt["expires_date_ms"].doubleValue / 1000
if expiration > expiresTime {
expiresTime = expiration
}
}
let currentTime = NSDate().timeIntervalSince1970
let expired = currentTime > expiresTime
if expired {
subscribed = false
} else {
subscribed = true
}
} catch {
print(error)
}
default:
print("Error code: \(httpResponse.statusCode)")
}
}
task.resume()
} catch {
// May be because there is no history of subscription
print(error)
}
} else {
print("no receipt")
}
}
You can find the line of the crash by symbolicating your crash report.
When you do that you'll be able to figure out what is causing the crash and take it from there.
You can integrate Crashlytics in your app, next time if app will crash at app store , you will get class name and line number on your email where your app has crashed.

data is not coming JSON parsing

let url1 = "https://jsonplaceholder.typicode.com/posts"
var request = URLRequest(url: URL(string: url1)!)
request.httpMethod = "GET"
let urlSession = URLSession.shared
let task = urlSession.dataTask(with: request, completionHandler: {(data,response,error) -> Void in
if let error = error {
print(error)
return
}
if let data = data {
OperationQueue.main.addOperation({ () -> Void in
self.tableView.reloadData()
})
}
})
task.resume()
With or without http method, response data is empty. What am I doing wrong? WiFi works fine. Maybe problem on my simulator settings?
let url = URL(string: "https://jsonplaceholder.typicode.com/posts")
URLSession.shared.dataTask(with:url!, completionHandler: {(data, response, error) in
guard let data = data, error == nil else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
let posts = json as? [[String: Any]] ?? []
print(posts)
for post in posts{
let product = Product()
product.userId = post["userId"] as! Int
product.id = post["id"] as! Int
product.title = post["title"] as! String
product.body = post["body"] as! String
self.products.append(product)
}
} catch let error as NSError {
print(error)
}
}).resume()
Yeah thanks, but your code is not works too, i mean say data is empty, nothing to be parsed. WiFi on my emulator works fine maybe problem on my xcode8?
Made some changes in your code.
let url = NSURL(string: "https://jsonplaceholder.typicode.com/posts")
NSURLSession.sharedSession().dataTaskWithRequest(NSURLRequest.init(URL: url!), completionHandler: {(data, response, error) in
guard let data = data where error == nil else { return }
do {
let posts = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)
print(posts)
} catch let error as NSError {
print(error)
}
}).resume()
Output :
Here your all 100 post display

Implementing Receipt Validation in Swift 3

I am developing an iOS app in Swift 3 and trying to implement receipt validation following this tutorial: http://savvyapps.com/blog/how-setup-test-auto-renewable-subscription-ios-app. However, the tutorial seems to have been written using an earlier version of Swift, so I had to make several changes. Here is my receiptValidation() function:
func receiptValidation() {
let receiptPath = Bundle.main.appStoreReceiptURL?.path
if FileManager.default.fileExists(atPath: receiptPath!){
var receiptData:NSData?
do{
receiptData = try NSData(contentsOf: Bundle.main.appStoreReceiptURL!, options: NSData.ReadingOptions.alwaysMapped)
}
catch{
print("ERROR: " + error.localizedDescription)
}
let receiptString = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
let postString = "receipt-data=" + receiptString! + "&password=" + SUBSCRIPTION_SECRET
let storeURL = NSURL(string:"https://sandbox.itunes.apple.com/verifyReceipt")!
let storeRequest = NSMutableURLRequest(url: storeURL as URL)
storeRequest.httpMethod = "POST"
storeRequest.httpBody = postString.data(using: .utf8)
let session = URLSession(configuration:URLSessionConfiguration.default)
let task = session.dataTask(with: storeRequest as URLRequest) { data, response, error in
do{
let jsonResponse:NSDictionary = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
let expirationDate:NSDate = self.expirationDateFromResponse(jsonResponse: jsonResponse)!
self.updateIAPExpirationDate(date: expirationDate)
}
catch{
print("ERROR: " + error.localizedDescription)
}
}
task.resume()
}
}
The problem shows up when I try to call the expirationDateFromResponse() method. It turns out that the jsonResponse that gets passed to this method only contains: status = 21002;. I looked this up and it means "The data in the receipt-data property was malformed or missing." However, the device I'm testing on has an active sandbox subscription for the product, and the subscription seems to work correctly aside from this issue. Is there something else I still need to do to make sure the receiptData value will be read and encoded correctly, or some other issue that might be causing this problem?
EDIT:
I tried an alternate way of setting storeRequest.httpBody:
func receiptValidation() {
let receiptPath = Bundle.main.appStoreReceiptURL?.path
if FileManager.default.fileExists(atPath: receiptPath!){
var receiptData:NSData?
do{
receiptData = try NSData(contentsOf: Bundle.main.appStoreReceiptURL!, options: NSData.ReadingOptions.alwaysMapped)
}
catch{
print("ERROR: " + error.localizedDescription)
}
let receiptString = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0)) //.URLEncoded
let dict = ["receipt-data":receiptString, "password":SUBSCRIPTION_SECRET] as [String : Any]
var jsonData:Data?
do{
jsonData = try JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted)
}
catch{
print("ERROR: " + error.localizedDescription)
}
let storeURL = NSURL(string:"https://sandbox.itunes.apple.com/verifyReceipt")!
let storeRequest = NSMutableURLRequest(url: storeURL as URL)
storeRequest.httpMethod = "POST"
storeRequest.httpBody = jsonData!
let session = URLSession(configuration:URLSessionConfiguration.default)
let task = session.dataTask(with: storeRequest as URLRequest) { data, response, error in
do{
let jsonResponse:NSDictionary = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
let expirationDate:NSDate = self.expirationDateFromResponse(jsonResponse: jsonResponse)!
self.updateIAPExpirationDate(date: expirationDate)
}
catch{
print("ERROR: " + error.localizedDescription)
}
}
task.resume()
}
}
However, when I run the app with this code, it hangs upon reaching the line jsonData = try JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted). It doesn't even make it to the catch block, it just stops doing anything. From what I've seen online, other people seem to have trouble using JSONSerialization.data to set the request httpBody in Swift 3.
Its working correctly with Swift 4
func receiptValidation() {
let SUBSCRIPTION_SECRET = "yourpasswordift"
let receiptPath = Bundle.main.appStoreReceiptURL?.path
if FileManager.default.fileExists(atPath: receiptPath!){
var receiptData:NSData?
do{
receiptData = try NSData(contentsOf: Bundle.main.appStoreReceiptURL!, options: NSData.ReadingOptions.alwaysMapped)
}
catch{
print("ERROR: " + error.localizedDescription)
}
//let receiptString = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
let base64encodedReceipt = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions.endLineWithCarriageReturn)
print(base64encodedReceipt!)
let requestDictionary = ["receipt-data":base64encodedReceipt!,"password":SUBSCRIPTION_SECRET]
guard JSONSerialization.isValidJSONObject(requestDictionary) else { print("requestDictionary is not valid JSON"); return }
do {
let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt" // this works but as noted above it's best to use your own trusted server
guard let validationURL = URL(string: validationURLString) else { print("the validation url could not be created, unlikely error"); return }
let session = URLSession(configuration: URLSessionConfiguration.default)
var request = URLRequest(url: validationURL)
request.httpMethod = "POST"
request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringCacheData
let task = session.uploadTask(with: request, from: requestData) { (data, response, error) in
if let data = data , error == nil {
do {
let appReceiptJSON = try JSONSerialization.jsonObject(with: data)
print("success. here is the json representation of the app receipt: \(appReceiptJSON)")
// if you are using your server this will be a json representation of whatever your server provided
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
} else {
print("the upload task returned an error: \(error)")
}
}
task.resume()
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
}
}
I have updated the #user3726962's code, removing unnecessary NS'es and "crash operators". It should look more like Swift 3 now.
Before using this code be warned that Apple doesn't recommend doing direct [device] <-> [Apple server] validation and asks to do it [device] <-> [your server] <-> [Apple server]. Use only if you are not afraid to have your In-App Purchases hacked.
UPDATE: Made the function universal: it will attempt to validate receipt with Production first, if fails - it will repeat with Sandbox. It's a bit bulky, but should be quite self-contained and independent from 3rd-parties.
func tryCheckValidateReceiptAndUpdateExpirationDate() {
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
NSLog("^A receipt found. Validating it...")
GlobalVariables.isPremiumInAmbiquousState = true // We will allow user to use all premium features until receipt is validated
// If we have problems validating the purchase - this is not user's fault
do {
let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
let receiptString = receiptData.base64EncodedString(options: [])
let dict = ["receipt-data" : receiptString, "password" : "your_shared_secret"] as [String : Any]
do {
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted)
if let storeURL = Foundation.URL(string:"https://buy.itunes.apple.com/verifyReceipt"),
let sandboxURL = Foundation.URL(string: "https://sandbox.itunes.apple.com/verifyReceipt") {
var request = URLRequest(url: storeURL)
request.httpMethod = "POST"
request.httpBody = jsonData
let session = URLSession(configuration: URLSessionConfiguration.default)
NSLog("^Connecting to production...")
let task = session.dataTask(with: request) { data, response, error in
// BEGIN of closure #1 - verification with Production
if let receivedData = data, let httpResponse = response as? HTTPURLResponse,
error == nil, httpResponse.statusCode == 200 {
NSLog("^Received 200, verifying data...")
do {
if let jsonResponse = try JSONSerialization.jsonObject(with: receivedData, options: JSONSerialization.ReadingOptions.mutableContainers) as? Dictionary<String, AnyObject>,
let status = jsonResponse["status"] as? Int64 {
switch status {
case 0: // receipt verified in Production
NSLog("^Verification with Production succesful, updating expiration date...")
self.updateExpirationDate(jsonResponse: jsonResponse) // Leaves isPremiumInAmbiquousState=true if fails
case 21007: // Means that our receipt is from sandbox environment, need to validate it there instead
NSLog("^need to repeat evrything with Sandbox")
var request = URLRequest(url: sandboxURL)
request.httpMethod = "POST"
request.httpBody = jsonData
let session = URLSession(configuration: URLSessionConfiguration.default)
NSLog("^Connecting to Sandbox...")
let task = session.dataTask(with: request) { data, response, error in
// BEGIN of closure #2 - verification with Sandbox
if let receivedData = data, let httpResponse = response as? HTTPURLResponse,
error == nil, httpResponse.statusCode == 200 {
NSLog("^Received 200, verifying data...")
do {
if let jsonResponse = try JSONSerialization.jsonObject(with: receivedData, options: JSONSerialization.ReadingOptions.mutableContainers) as? Dictionary<String, AnyObject>,
let status = jsonResponse["status"] as? Int64 {
switch status {
case 0: // receipt verified in Sandbox
NSLog("^Verification succesfull, updating expiration date...")
self.updateExpirationDate(jsonResponse: jsonResponse) // Leaves isPremiumInAmbiquousState=true if fails
default: self.showAlertWithErrorCode(errorCode: status)
}
} else { DebugLog("Failed to cast serialized JSON to Dictionary<String, AnyObject>") }
}
catch { DebugLog("Couldn't serialize JSON with error: " + error.localizedDescription) }
} else { self.handleNetworkError(data: data, response: response, error: error) }
}
// END of closure #2 = verification with Sandbox
task.resume()
default: self.showAlertWithErrorCode(errorCode: status)
}
} else { DebugLog("Failed to cast serialized JSON to Dictionary<String, AnyObject>") }
}
catch { DebugLog("Couldn't serialize JSON with error: " + error.localizedDescription) }
} else { self.handleNetworkError(data: data, response: response, error: error) }
}
// END of closure #1 - verification with Production
task.resume()
} else { DebugLog("Couldn't convert string into URL. Check for special characters.") }
}
catch { DebugLog("Couldn't create JSON with error: " + error.localizedDescription) }
}
catch { DebugLog("Couldn't read receipt data with error: " + error.localizedDescription) }
} else {
DebugLog("No receipt found even though there is an indication something has been purchased before")
NSLog("^No receipt found. Need to refresh receipt.")
self.refreshReceipt()
}
}
func refreshReceipt() {
let request = SKReceiptRefreshRequest()
request.delegate = self // to be able to receive the results of this request, check the SKRequestDelegate protocol
request.start()
}
This works for auto-renewable subscriptions. Haven't tested it with other kinds of subscriptions yet. Leave a comment if it works for you with some other subscription type.
//too low rep to comment
Yasin Aktimur, thanks for your answer, it's awesome. However, looking at Apple documentation on this, they say to connect to iTunes on a separate Queue. So it should look like this:
func receiptValidation() {
let SUBSCRIPTION_SECRET = "secret"
let receiptPath = Bundle.main.appStoreReceiptURL?.path
if FileManager.default.fileExists(atPath: receiptPath!){
var receiptData:NSData?
do{
receiptData = try NSData(contentsOf: Bundle.main.appStoreReceiptURL!, options: NSData.ReadingOptions.alwaysMapped)
}
catch{
print("ERROR: " + error.localizedDescription)
}
let base64encodedReceipt = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions.endLineWithCarriageReturn)
let requestDictionary = ["receipt-data":base64encodedReceipt!,"password":SUBSCRIPTION_SECRET]
guard JSONSerialization.isValidJSONObject(requestDictionary) else { print("requestDictionary is not valid JSON"); return }
do {
let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt" // this works but as noted above it's best to use your own trusted server
guard let validationURL = URL(string: validationURLString) else { print("the validation url could not be created, unlikely error"); return }
let session = URLSession(configuration: URLSessionConfiguration.default)
var request = URLRequest(url: validationURL)
request.httpMethod = "POST"
request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringCacheData
let queue = DispatchQueue(label: "itunesConnect")
queue.async {
let task = session.uploadTask(with: request, from: requestData) { (data, response, error) in
if let data = data , error == nil {
do {
let appReceiptJSON = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSDictionary
print("success. here is the json representation of the app receipt: \(appReceiptJSON)")
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
} else {
print("the upload task returned an error: \(error ?? "couldn't upload" as! Error)")
}
}
task.resume()
}
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
}
}
I struggled my head with the same problem. The issue is that this line:
let receiptString = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
Returns an OPTIONAL and
jsonData = try JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted)
cannot handle optionals. So to fix it, simply substitute the first line of code with this:
let receiptString:String = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength64Characters) as String!
And everything will work like charm!
I liked your answer and I just rewrote it in C# for those who are using it like me as I did not find a good source for the solution.
Thanks Again
For Consumable IAP
void ReceiptValidation()
{
var recPath = NSBundle.MainBundle.AppStoreReceiptUrl.Path;
if (File.Exists(recPath))
{
NSData recData;
NSError error;
recData = NSData.FromUrl(NSBundle.MainBundle.AppStoreReceiptUrl, NSDataReadingOptions.MappedAlways, out error);
var recString = recData.GetBase64EncodedString(NSDataBase64EncodingOptions.None);
var dict = new Dictionary<String,String>();
dict.TryAdd("receipt-data", recString);
var dict1 = NSDictionary.FromObjectsAndKeys(dict.Values.ToArray(), dict.Keys.ToArray());
var storeURL = new NSUrl("https://sandbox.itunes.apple.com/verifyReceipt");
var storeRequest = new NSMutableUrlRequest(storeURL);
storeRequest.HttpMethod = "POST";
var jsonData = NSJsonSerialization.Serialize(dict1, NSJsonWritingOptions.PrettyPrinted, out error);
if (error == null)
{
storeRequest.Body = jsonData;
var session = NSUrlSession.FromConfiguration(NSUrlSessionConfiguration.DefaultSessionConfiguration);
var tsk = session.CreateDataTask(storeRequest, (data, response, err) =>
{
if (err == null)
{
var rstr = NSJsonSerialization.FromObject(data);
}
else
{
// Check Error
}
});
tsk.Resume();
}else
{
// JSON Error Handling
}
}
}
Eventually I was able to solve the problem by having my app call a Lambda function written in Python, as shown in this answer. I'm still not sure what was wrong with my Swift code or how to do this entirely in Swift 3, but the Lambda function got the desired result in any case.

Resources