【问题标题】:IAPs actually validating the receipt (Swift)IAP 实际验证收据 (Swift)
【发布时间】:2015-11-24 08:01:23
【问题描述】:

我一直在尝试在我的 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 编写的。

如果有人成功使用收据验证可以帮助我朝着正确的方向前进,我们将不胜感激。非常感谢

更新:

在得到 JSA986I 和 cbartel 的出色回答后,我将其变成了 github 上的助手。非常感谢您的帮助

https://github.com/crashoverride777/SwiftyReceiptValidator

【问题讨论】:

  • 你能得到这个工作吗?如果是这样,您介意分享您完成的代码吗?我现在正在解决同样的问题。非常感谢!
  • 嘿,很遗憾我现在已经放弃了收据验证。上面的代码是正确的代码减去我为 swift 2 所做的一些更改(使用保护语句)。我只是不明白最后一点验证,关于这个的教程很少。
  • 感谢您的回复。我已经取得了一些进展,如果我能够取得更多进展,我会在这里分享代码。
  • @crashoverride777 - 感谢您创建了一个很棒的帮助文件。助手工作出色。但是,沙盒测试失败,错误代码为 21002。经过调试和花费大量时间。知道这是由于“付费应用”协议状态不是“活动”而发生的。如果您可以在使用该实用程序之前记录检查协议状态,一定会有所帮助!

标签: swift validation in-app-purchase receipt


【解决方案1】:

这就是您以 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)")
}

【讨论】:

  • 天哪,非常感谢。它一直在引起我的注意,关于它的信息对我来说不是很清楚。我知道“...int == 0”位,但第二个代码“如果让收据..”正是我想要的。所以我假设如果我想检查捆绑包,我需要写“... parseJSON["bundle_id"] as? NSArray {...." 还是我需要使用常量 lastReceipt 来检查捆绑包 ID ?
  • 奇怪的是“int == 0”的东西有效,但“如果让receiptInfo”对我不起作用,它不会在之后调用println。很抱歉打扰你,但我觉得我很接近。
  • 谢谢,我会检查一下。我最近没有时间检查您的建议。我会尽快将其标记为已回答。非常感谢
  • 没问题希望有用
【解决方案2】:

这是使用 Swift 2.1 版的解决方案,遵循Apple guide

苹果也recommends the following:

在您的服务器上验证收据时,您的服务器需要能够处理从 Apple 测试环境获取其收据的生产签名应用。推荐的方法是让您的生产服务器始终首先根据生产 App Store 验证收据。如果验证失败并出现错误代码“Sandbox receipt used in production”,请改为针对测试环境进行验证。

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

【讨论】:

  • 嘿,非常感谢,我会再试一次。
【解决方案3】:

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

这是我在 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);

希望对您有所帮助。

【讨论】:

    猜你喜欢
    • 2015-01-08
    • 1970-01-01
    • 2018-06-16
    • 2016-06-24
    • 1970-01-01
    • 2012-05-04
    • 1970-01-01
    • 2017-02-04
    • 1970-01-01
    相关资源
    最近更新 更多