0

I'm working on a subscription IAP. I set all purchases up, their details come back well, I can do the purchases in sandbox and get all the messages alright. The problem I have now is checking the receipt. I always get a URL returned alright, but when I try to read it I keep getting error that the file does not exist. So I try and refresh with SKReceiptRefreshRequest. Try again, still same.

I have uninstalled app on simulator and two real devices, try again from new install and same problem. One thing I realised, one of the real devices displays the password prompt request with [Sandbox] mention. However after two prompts (including accepting password), instead of purchase completed I get a "user/password don't match" message. On simulator when prompted for itunes account and password it all goes through but the actual purchase confirmation never comes (I waited 4 minutes, stable internet connection).

This is the validation process (I have changed it quite a few times, from different tutorials and other people's problems)

let receiptURL = Bundle.main.appStoreReceiptURL
func receiptValidation() {
    print("1")
        print("2", receiptURL)
        do {
            print("3")
            let receiptData = try Data(contentsOf: receiptURL!, options: .alwaysMapped)
            print(receiptData)
            let receiptString = receiptData.base64EncodedString(options: [])
            let dict = ["receipt-data" : receiptString, "password" : "\(password)"] as [String : Any]

            do {
                print("4")
                let jsonData = try JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted)

                if let sandboxURL = Foundation.URL(string:"https://sandbox.itunes.apple.com/verifyReceipt") {
                    print("5")
                    var request = URLRequest(url: sandboxURL)
                    request.httpMethod = "POST"
                    request.httpBody = jsonData
                    let session = URLSession(configuration: URLSessionConfiguration.default)
                    let task = session.dataTask(with: request) { data, response, error in
                        print("6")
                        if let receivedData = data,
                            let httpResponse = response as? HTTPURLResponse,
                            error == nil,
                            httpResponse.statusCode == 200 {
                            print("7")
                            do {
                                print("8")
                                if let jsonResponse = try JSONSerialization.jsonObject(with: receivedData, options: JSONSerialization.ReadingOptions.mutableContainers) as? Dictionary<String, AnyObject> {
                                    print(jsonResponse, jsonResponse.count)
                                    // parse and verify the required informatin in the jsonResponse

                                } else { print("Failed to cast serialized JSON to Dictionary<String, AnyObject>") }
                            }
                            catch { print("Couldn't serialize JSON with error: " + error.localizedDescription) }
                        }
                    }
                    print("51")
                    task.resume()
                } else { print("Couldn't convert string into URL. Check for special characters.") }
            }
            catch { print("Couldn't create JSON with error: " + error.localizedDescription) }
        }
        catch {
            let appReceiptRefreshRequest = SKReceiptRefreshRequest(receiptProperties: nil)
            appReceiptRefreshRequest.delegate = self
            appReceiptRefreshRequest.start()
            print("Couldn't read receipt data with error: " + error.localizedDescription) }
    }

func requestDidFinish(_ request: SKRequest) {
    print("???")
    do {
        let receipt = try Data(contentsOf: receiptURL!) //force unwrap is safe here, control can't land here if receiptURL is nil
        print(receipt)
    } catch {
        print("WTF NO RECEIPT")
        // still no receipt, possible but unlikely to occur since this is the "success" delegate method
    }
}

And this is the Debugging output from running the app. receiptURL varies between simulator/real device, but other then that everything remains the same.

1
2 Optional(file:///Users/apple/Library/Developer/CoreSimulator/Devices/47EA3293-9B13-4808-BD0B-13D884D14BFE/data/Containers/Data/Application/2F1B7E4E-C523-4270-BF46-6D77F7A2220C/StoreKit/receipt)
3
Couldn't read receipt data with error: The file “receipt” couldn’t be opened because there is no such file.
???
WTF NO RECEIPT
???
WTF NO RECEIPT

Why can't I get the receipt created, or found? Is it a device problem, a bug or am I oblivious to something?

Alex Ioja-Yang
  • 1,428
  • 13
  • 28

2 Answers2

1

The whole IAP process works asynchronously due to which you will not receive the receipt data unless the whole process has been completed successfully. I can't see the whole code based on what you have pasted in your question above but if you are trying to access the receipt data immediately on the action of a button or something similar, you will not get it.

The correct way to access receipt data is to try accessing the receipt based on success completion handler callback of your IAP request. Once you submit the IAP request there is a server side process which takes care of processing the IAP and then a callback handler from IAP SKPaymentTransactionObserver class is triggered. Using the notification handler from this class you can send the update to your ViewController to check for receipt data.

Gurdev Singh
  • 1,996
  • 13
  • 11
  • So you're saying when payment is completed and I get a transaction.state of .purchased, I should download the receipt? I thought the receipt gets downloaded at the point of purchase automatically. And even so, why the SKReceiptRefreshRequest does not work and every time it assigns an appStoreReceiptURL but at reading time it fails? – Alex Ioja-Yang Apr 25 '17 at 12:58
0

While further researching, I have found the following article, which solved the problem

article

Important: If you mistakenly use a sandbox tester account to log in to a production environment on your test device instead of your test environment, the sandbox account becomes invalid and can’t be used again. If this happens, create a new sandbox tester account with a new email address.

Alex Ioja-Yang
  • 1,428
  • 13
  • 28