IAP 实际验证收据 (Swift)
IAPs actually validating the receipt (Swift)
我一直在尝试在我的 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 上的一个助手。非常感谢您的帮助
那是您 return 您的收据 JSON 并且可以访问它的地方。即
if parseJSON["status"] as? Int == 0 {
println("Sucessfully returned purchased receipt data")
}
会告诉您是否已成功收到收据,因为["status"] 0
表示它已经returned ok
您可以进一步查询和使用收据数据来查找和使用 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)")
Post上面的代码在
下
if let parseJSON = json {
println("Receipt \(parseJSON)")
}
这是在 Apple guide.
之后使用 Swift 2.1 版的解决方案
When validating receipts on your server, your server needs to be able to handle a production-signed app getting its receipts from Apple’s test environment. The recommended approach is for your production server to always validate receipts against the production App Store first. If validation fails with the error code “Sandbox receipt used in production”, validate against the test environment instead.
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)
}
}
}
今天,我被这个问题困扰了。
我参考了这个答案。
但是我发现了检查订阅是否过期的新方法。
这是我在 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);
希望对您有所帮助。
我一直在尝试在我的 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 上的一个助手。非常感谢您的帮助
那是您 return 您的收据 JSON 并且可以访问它的地方。即
if parseJSON["status"] as? Int == 0 {
println("Sucessfully returned purchased receipt data")
}
会告诉您是否已成功收到收据,因为["status"] 0
表示它已经returned ok
您可以进一步查询和使用收据数据来查找和使用 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)")
Post上面的代码在
下if let parseJSON = json {
println("Receipt \(parseJSON)")
}
这是在 Apple guide.
之后使用 Swift 2.1 版的解决方案When validating receipts on your server, your server needs to be able to handle a production-signed app getting its receipts from Apple’s test environment. The recommended approach is for your production server to always validate receipts against the production App Store first. If validation fails with the error code “Sandbox receipt used in production”, validate against the test environment instead.
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)
}
}
}
今天,我被这个问题困扰了。 我参考了这个答案。 但是我发现了检查订阅是否过期的新方法。
这是我在 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);
希望对您有所帮助。