IAP实际验证收据(Swift)

时间:2015-08-29 23:31:49

标签: swift validation in-app-purchase receipt

我一直在尝试在我的spritekit游戏中实现收据验证。我一直在关注各种教程,基本上最终得到了这段代码

enum RequestURL: String {
   case production = "https://buy.itunes.apple.com/verifyReceipt"
   case sandbox = "https://sandbox.itunes.apple.com/verifyReceipt"
   case myServer = "my server address"
}

enum ReceiptStatusCode: Int {

// Not decodable status
case unknown = -2

// No status returned
case none = -1

// valid status
case valid = 0

// The App Store could not read the JSON object you provided.
case JSONNotReadable = 21000

// The data in the receipt-data property was malformed or missing.
case malformedOrMissingData = 21002

// The receipt could not be authenticated.
case receiptCouldNotBeAuthenticated = 21003

// The shared secret you provided does not match the shared secret on file for your account.
// Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions.
case sharedSecretNotMatching = 21004

// The receipt server is currently not available.
case receiptServerUnavailable = 21005

// This receipt is valid but the subscription has expired. When this status code is returned to your server, the receipt data is also decoded and returned as part of the response.
// Only returned for iOS 6 style transaction receipts for auto-renewable subscriptions.
case subscriptionExpired = 21006

//  This receipt is from the test environment, but it was sent to the production environment for verification. Send it to the test environment instead.
case testReceipt = 21007

// This receipt is from the production environment, but it was sent to the test environment for verification. Send it to the production environment instead.
case productionEnvironment = 21008
 }

  func validateReceipt(forTransaction transaction: SKPaymentTransaction) {

    guard let receiptURL = NSBundle.mainBundle().appStoreReceiptURL else { return }

    guard let receipt = NSData(contentsOfURL: receiptURL) else { return }

    let receiptData = receipt.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
    let payload = ["receipt-data": receiptData]

    var receiptPayloadData: NSData?

    do {
        receiptPayloadData = try NSJSONSerialization.dataWithJSONObject(payload, options: NSJSONWritingOptions(rawValue: 0))
    }
    catch let error as NSError {
        print(error.localizedDescription)
        return
    }

    guard let payloadData = receiptPayloadData else { return }
    guard let requestURL = NSURL(string: RequestURL.sandbox.rawValue) else { return }

    let request = NSMutableURLRequest(URL: requestURL)
    request.HTTPMethod = "POST"
    request.HTTPBody = payloadData

    let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) in
         if let error = error {
            print(error.localizedDescription)
            return
        }  
         guard let data = data else { return }          

         do {
            let jsonData = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableLeaves) as? NSDictionary

            guard let json = jsonData else { return }

            // Correct ?
            guard let status = json["status"] as? Int where status == ReceiptStatusCode.valid.rawValue else { return }

            // Unlock product here?
            // Other checks needed?
        }

        catch let error as NSError {
            print(error.localizedDescription)
            return
        }
     }

    task.resume()
}

它是漂亮的锅炉板代码并按预期工作。我现在的问题是我不知道如何在最后一步(标记线)实际验证收据。 我相信我现在必须进行5次左右的检查以验证收据。我不知道他们中的大部分将如何在swift中完成。大多数教程都是旧的,不包括这一步,或者不是用swift编写的。

如果任何成功使用收据验证的人都可以帮助我朝着正确的方向前进,那将非常感激。非常感谢你

更新:

在JSA986I和cbartel得到了很好的答案后,我把它变成了github上的帮手。非常感谢您的帮助

https://github.com/crashoverride777/SwiftyReceiptValidator

3 个答案:

答案 0 :(得分:8)

以下是使用Apple guide之后的Swift 2.1版的解决方案。

Apple还建议如下:

“验证服务器上的收据时,您的服务器需要能够处理生产签名的应用程序从Apple的测试环境中获取收据。建议的方法是让您的生产服务器始终首先验证生产App Store的收据。如果验证失败并显示错误代码“生产中使用的沙盒收据”,请改为对测试环境进行验证。“

validateReceipt(NSBundle.mainBundle().appStoreReceiptURL) { (success: Bool) -> Void in
            print(success)
        }

private func receiptData(appStoreReceiptURL : NSURL?) -> NSData? {

    guard let receiptURL = appStoreReceiptURL,
        receipt = NSData(contentsOfURL: receiptURL) else {
            return nil
    }

    do {
        let receiptData = receipt.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
        let requestContents = ["receipt-data" : receiptData]
        let requestData = try NSJSONSerialization.dataWithJSONObject(requestContents, options: [])
        return requestData
    }
    catch let error as NSError {
        print(error)
    }

    return nil
}

private func validateReceiptInternal(appStoreReceiptURL : NSURL?, isProd: Bool , onCompletion: (Int?) -> Void) {

    let serverURL = isProd ? "https://buy.itunes.apple.com/verifyReceipt" : "https://sandbox.itunes.apple.com/verifyReceipt"

    guard let receiptData = receiptData(appStoreReceiptURL),
        url = NSURL(string: serverURL)  else {
        onCompletion(nil)
        return
    }

    let request = NSMutableURLRequest(URL: url)
    request.HTTPMethod = "POST"
    request.HTTPBody = receiptData

    let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in

        guard let data = data where error == nil else {
            onCompletion(nil)
            return
        }

        do {
            let json = try NSJSONSerialization.JSONObjectWithData(data, options:[])
            print(json)
            guard let statusCode = json["status"] as? Int else {
                onCompletion(nil)
                return
            }
            onCompletion(statusCode)
        }
        catch let error as NSError {
            print(error)
            onCompletion(nil)
        }
    })
    task.resume()
}

public func validateReceipt(appStoreReceiptURL : NSURL?, onCompletion: (Bool) -> Void) {

    validateReceiptInternal(appStoreReceiptURL, isProd: true) { (statusCode: Int?) -> Void in
        guard let status = statusCode else {
            onCompletion(false)
            return
        }

        // This receipt is from the test environment, but it was sent to the production environment for verification.
        if status == 21007 {
            self.validateReceiptInternal(appStoreReceiptURL, isProd: false) { (statusCode: Int?) -> Void in
                guard let statusValue = statusCode else {
                    onCompletion(false)
                    return
                }

                // 0 if the receipt is valid
                if statusValue == 0 {
                    onCompletion(true)
                } else {
                    onCompletion(false)
                }

            }

        // 0 if the receipt is valid
        } else if status == 0 {
            onCompletion(true)
        } else {
            onCompletion(false)
        }
    }
}

答案 1 :(得分:4)

这就是您将收据作为JSON返回并可以访问它的地方。即

if parseJSON["status"] as? Int == 0 {
                       println("Sucessfully returned purchased receipt data")
                    }

如果您已成功收到收据,将告诉您,因为["status"] 0表示已将其退回确定

您可以进一步查询和使用收据数据来查找和使用JSON响应中的项目。在这里,您可以打印最新的收据信息

if let receiptInfo: NSArray = parseJSON["latest_receipt_info"] as? NSArray {
                       let lastReceipt = receiptInfo.lastObject as! NSDictionary

                        // Get last receipt
                        println("LAST RECEIPT INFORMATION \n",lastReceipt)
}

现在您要使用您的json数据

您现在可以查询该数据,在此示例中,我们发现订阅在JSOn响应中的自动续订订阅到期时

 // Format date
 var formatter = NSDateFormatter()
 formatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV"
 formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")

 // Get Expiry date as NSDate
 let subscriptionExpirationDate: NSDate = formatter.dateFromString(lastReceipt["expires_date"] as! String) as NSDate!
  println("\n   - DATE SUBSCRIPTION EXPIRES = \(subscriptionExpirationDate)")

下发布上述代码
if let parseJSON = json {
                println("Receipt \(parseJSON)")

答案 2 :(得分:0)

今天,我遇到了这个问题。 我引用了这个答案。 但我发现检查订阅的新方法是否过期。

这是我在Objective-C中的代码。

NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:resData options:0 error:&error]; 

// this is response from AppStore
NSDictionary *dictLatestReceiptsInfo = jsonResponse[@"latest_receipt_info"];
long long int expirationDateMs = [[dictLatestReceiptsInfo valueForKeyPath:@"@max.expires_date_ms"] longLongValue];
long long requestDateMs = [jsonResponse[@"receipt"][@"request_date_ms"] longLongValue];
isValidReceipt = [[jsonResponse objectForKey:@"status"] integerValue] == 0 && (expirationDateMs > requestDateMs);

希望得到这个帮助。