Implementing Receipt Validation in Swift 3

后端 未结 6 691
没有蜡笔的小新
没有蜡笔的小新 2020-12-24 09:59

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-subscriptio

相关标签:
6条回答
  • 2020-12-24 10:32

    //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)")
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-24 10:34

    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)")
            }
    
    
    
        }
    }
    
    0 讨论(0)
  • 2020-12-24 10:34

    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!

    0 讨论(0)
  • 2020-12-24 10:38

    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.

    0 讨论(0)
  • 2020-12-24 10:42

    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.

    0 讨论(0)
  • 2020-12-24 10:47

    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
                }
            }
        }
    
    0 讨论(0)
提交回复
热议问题