2016-09-26 116 views
1

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

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)。它甚至不会让它进入catch块,它只​​是停止做任何事情。从我在网上看到,其他人似乎无法使用JSONSerialization.data来设置Swift 3中的httpBody请求。

+0

请确保您已编码base64编码收据中的任何+字符的%编码。即用+%2b替换+的实例 – Paulw11

+0

我更改了我的代码,在声明后立即对收件人字符串进行了此更改,但仍然看到相同的错误。另外,当我打印出receiptString时,我注意到它包含了很多用于分隔64位长字符串的“/”字符。这是如何正确编码应该看起来? – user3726962

+0

我还应该提到,我尝试删除“/”字符,但我仍然看到21002状态。 – user3726962

回答

0

最终我能够通过让我的应用调用用Python编写的Lambda函数来解决问题,如this所示。我仍然不确定我的Swift代码出了什么问题,或者完全在Swift 3中完成这个工作,但是Lambda函数在任何情况下都能得到所需的结果。

0

我用同样的问题挣扎着我的头。问题是,这条线:

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

返回一个可选的

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

不能处理选配。所以要解决它,只需用下面的代码替换第一行代码:

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

而且一切都会像魅力一样工作!

+0

这很奇怪,但您的解决方案返回错误21002,而问题中给出的原始代码完美工作(即使收件箱字符串是可选的)。我并不下调,因为根据购买类型或我还没有意识到的其他因素,可能会有一些细微的差异,所以有人仍然可能会发现此答案有帮助。 – Vitalii

+0

@VitaliiTymoshenko在原始问题receiptData是一个可选的,它不能传递给JSONSerialization.data(...)。也许你的问题是另一个。 –

+0

您的建议代码使用.lineLength64Characters。我已经仔细检查过,从这个选项中获取数据的字符串会为我产生错误21002。我同意最初的代码有可选的(我在调试时已经清楚地看到了它),但是我也很惊讶它以某种方式正确地为我工作。所以我最终使用了默认编码方法 - receiptData.base64EncodedString(options:[])。请在下面的答案中查看完整的代码。 – Vitalii

4

我已更新@ user3726962的代码,删除不必要的NS和“碰撞操作员”。它现在应该看起来更像Swift 3

使用此代码之前警告说,苹果不建议这样做直接的[装置] < - > [苹果服务器]验证,并要求做[装置] < - > [您的服务器] < - > [苹果服务器]。只有在您不害怕让您的应用程序内购买黑客入侵时才使用。

这只是一个例子。最终验证应与Production和Sandbox一起使用。 Apple建议首先对生产进行验证。

func receiptValidation() { 
    if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL, 
     FileManager.default.fileExists(atPath: appStoreReceiptURL.path) { 

     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 sandboxURL = Foundation.URL(string:"https://sandbox.itunes.apple.com/verifyReceipt") { 
        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 
         if let receivedData = data, 
          let httpResponse = response as? HTTPURLResponse, 
          error == nil, 
          httpResponse.statusCode == 200 { 
          do { 
           if let jsonResponse = try JSONSerialization.jsonObject(with: receivedData, options: JSONSerialization.ReadingOptions.mutableContainers) as? Dictionary<String, AnyObject> { 

            // 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) } 
         } 
        } 
        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 { print("Couldn't read receipt data with error: " + error.localizedDescription) } 
    } 
} 

这适用于自动更新订阅。尚未用其他类型的订阅进行测试。如果它适用于其他订阅类型,请留下评论。

1

及其与斯威夫特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

//太低代表发表意见

亚辛Aktimur,感谢您的回答,它的真棒。然而,看着苹果公司的文档,他们说在一个单独的队列上连接到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)") 
     } 
    } 
}