在Swift 3

时间:2016-09-26 19:58:04

标签: ios swift in-app-purchase swift3 receipt-validation

我正在开发Swift 3中的iOS应用程序并尝试按照本教程实现收据验证:http://savvyapps.com/blog/how-setup-test-auto-renewable-subscription-ios-app。但是,该教程似乎是使用早期版本的Swift编写的,因此我不得不进行一些更改。这是我的receiptValidation()函数:

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()
    }
}

当我尝试调用expirationDateFromResponse()方法时,问题出现了。事实证明,传递给此方法的jsonResponse仅包含:status = 21002;。我查了一遍,这意味着"收据数据属性中的数据格式不正确或丢失。"但是,我正在测试的设备对该产品有一个有效的沙盒订阅,并且除了此问题之外,订阅似乎也能正常工作。还有什么我还需要做的事情,以确保将正确读取和编码receiptData值,或者可能导致此问题的其他一些问题吗?

编辑:

我尝试了另一种设置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()
    }
}

但是,当我使用此代码运行应用时,它会在到达jsonData = try JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted)行时挂起。它甚至没有进入捕获块,它只是停止做任何事情。从我在网上看到的,其他人似乎在使用JSONSerialization.data在Swift 3中设置请求httpBody时遇到了麻烦。

6 个答案:

答案 0 :(得分:8)

它与Swift 4正常工作

/getJsonFile

答案 1 :(得分:6)

我更新了@ user3726962的代码,删除了不必要的NS"崩溃运算符"。它应该看起来更像是 Swift 3

在使用此代码之前,请注意Apple不建议直接[设备]< - > [Apple服务器]验证并要求执行[设备]< - > [您的服务器]< - > [Apple服务器]。仅在您不害怕将应用程序内购买入侵时使用。

更新:使功能通用:它将尝试首先使用Production验证收据,如果失败 - 它将重复使用Sandbox。它有点笨重,但应该是独立的,独立于第三方。

select * from usr;
ID  | NAME
----|-----
2   | john
3   | sally
5   | mary

select * from items;
ID | NAME     |USERID  
---|----------|------
1  |myitem    | 2
2  |mynewitem | 2
3  |my-item   | 3
4  |mynew-item| 3

这适用于自动续订订阅。 Haven尚未对其他类型的订阅进行测试。如果它适用于某些其他订阅类型,请发表评论。

答案 2 :(得分:3)

我在同样的问题上挣扎着。问题是这一行:

let receiptString = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))

返回OPTIONAL和

jsonData = try JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted)

无法处理期权。所以要修复它,只需用第一行代码替换:

let receiptString:String = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength64Characters) as String!

一切都会像魅力一样发挥作用!

答案 3 :(得分:0)

最终我通过让我的应用程序调用用Python编写的Lambda函数来解决问题,如this回答所示。我仍然不确定我的Swift代码有什么问题或者如何在Swift 3中完全执行此操作,但Lambda函数无论如何都得到了理想的结果。

答案 4 :(得分:0)

//代表评论太低

Yasin Aktimur,谢谢你的回答,这太棒了。但是,看看关于这个的Apple文档,他们说要在单独的Queue上连接到iTunes。所以看起来应该是这样的:

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)")
        }
    }
}

答案 5 :(得分:0)

我喜欢您的答案,我只是用C#将其重写为那些像我一样使用它的人,因为我找不到解决方案的好来源。 再次感谢 对于消耗性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
            }
        }
    }