iOS 测试应用收据验证
Posted
技术标签:
【中文标题】iOS 测试应用收据验证【英文标题】:iOS test App Receipt Validation 【发布时间】:2016-09-30 16:46:01 【问题描述】:有很多关于如何使用沙盒测试员帐户测试应用内购买收据验证的示例。
但付费应用本身的收据如何?在开发环境中如何获取 App Receipt?
我想做两件事:
为了防止未购买该应用的用户非法复制我们的应用。 正如我所见,检测到已连接 iTune 帐户的应用程序不拥有该应用程序(它向用户显示警告,他们不拥有该应用程序,但他们未能阻止用户继续使用该应用程序)
将应用购买收据发送到我们的服务器。我们想知道他们什么时候购买我们的应用,他们带来了什么版本的应用。
【问题讨论】:
【参考方案1】:大部分答案都可以在 Apple 的文档中找到 here。但是存在差距,并且 Objective-c 代码正在使用已弃用的方法。
这段 Swift 3 代码展示了如何获取 App Receipt 并将其发送到应用商店进行验证。在保存所需数据之前,您绝对应该通过应用商店验证 App Receipt。要求应用商店进行验证的好处是,它会响应您可以轻松序列化为 JSON 的数据,并从那里提取您想要的键的值。不需要密码学。
正如 Apple 在该文档中描述的那样,首选流程是这样的......
device -> your trusted server -> app store -> your trusted server -> device
当应用商店返回您的服务器时,假设成功,您将在此处序列化并提取您需要的数据并按您的意愿保存。请参阅下面的 JSON。您可以将结果和您想要的任何其他内容发送回应用程序。
在下面的validateAppReceipt()
中,为了使其成为一个工作示例,它只是使用了这个流程......
device -> app store -> device
要使这项工作与您的服务器一起使用,只需将validationURLString
更改为指向您的服务器并将您需要的任何其他内容添加到requestDictionary
。
要在开发中对此进行测试,您需要:
确保您在 itunesconnect 中设置了沙盒用户 在您的测试设备上退出 iTunes 和 App Store 在测试期间,出现提示时,使用您的沙盒用户这是代码。幸福的道路流动得很好。错误和故障点只是打印或注释。根据需要处理那些。
这部分抓取应用收据。如果它不存在(在您测试时会发生),它会要求应用商店刷新。
let receiptURL = Bundle.main.appStoreReceiptURL
func getAppReceipt()
guard let receiptURL = receiptURL else /* receiptURL is nil, it would be very weird to end up here */ return
do
let receipt = try Data(contentsOf: receiptURL)
validateAppReceipt(receipt)
catch
// there is no app receipt, don't panic, ask apple to refresh it
let appReceiptRefreshRequest = SKReceiptRefreshRequest(receiptProperties: nil)
appReceiptRefreshRequest.delegate = self
appReceiptRefreshRequest.start()
// If all goes well control will land in the requestDidFinish() delegate method.
// If something bad happens control will land in didFailWithError.
func requestDidFinish(_ request: SKRequest)
// a fresh receipt should now be present at the url
do
let receipt = try Data(contentsOf: receiptURL!) //force unwrap is safe here, control can't land here if receiptURL is nil
validateAppReceipt(receipt)
catch
// still no receipt, possible but unlikely to occur since this is the "success" delegate method
func request(_ request: SKRequest, didFailWithError error: Error)
print("app receipt refresh request did fail with error: \(error)")
// for some clues see here: https://samritchie.net/2015/01/29/the-operation-couldnt-be-completed-sserrordomain-error-100/
这部分验证应用收据。这不是本地验证。请参阅 cmets 中的注 1 和注 2。
func validateAppReceipt(_ receipt: Data)
/* Note 1: This is not local validation, the app receipt is sent to the app store for validation as explained here:
https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW1
Note 2: Refer to the url above. For good reasons apple recommends receipt validation follow this flow:
device -> your trusted server -> app store -> your trusted server -> device
In order to be a working example the validation url in this code simply points to the app store's sandbox servers.
Depending on how you set up the request on your server you may be able to simply change the
structure of requestDictionary and the contents of validationURLString.
*/
let base64encodedReceipt = receipt.base64EncodedString()
let requestDictionary = ["receipt-data":base64encodedReceipt]
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)")
你应该得到这样的结果。在您的情况下,这就是您将在服务器上使用的内容。
environment = Sandbox;
receipt =
"adam_id" = 0;
"app_item_id" = 0;
"application_version" = "0"; // for me this was showing the build number rather than the app version, at least in testing
"bundle_id" = "com.yourdomain.yourappname"; // your app's actual bundle id
"download_id" = 0;
"in_app" = (
);
"original_application_version" = "1.0"; // this will always return 1.0 when testing, the real thing in production.
"original_purchase_date" = "2013-08-01 07:00:00 Etc/GMT";
"original_purchase_date_ms" = 1375340400000;
"original_purchase_date_pst" = "2013-08-01 00:00:00 America/Los_Angeles";
"receipt_creation_date" = "2016-09-21 18:46:39 Etc/GMT";
"receipt_creation_date_ms" = 1474483599000;
"receipt_creation_date_pst" = "2016-09-21 11:46:39 America/Los_Angeles";
"receipt_type" = ProductionSandbox;
"request_date" = "2016-09-22 18:37:41 Etc/GMT";
"request_date_ms" = 1474569461861;
"request_date_pst" = "2016-09-22 11:37:41 America/Los_Angeles";
"version_external_identifier" = 0;
;
status = 0;
【讨论】:
感谢您提供此示例!我一直在寻找很长一段时间,这完美无缺。 谢谢。非常非常有帮助。但是,没有经验的人(包括我自己)应该注意,如果您想将收据数据传递给受信任服务器上自己的 php 脚本,则必须先清理 base64 编码数据,然后再将其传递给 Apple 的验证。我仍在试图弄清楚如何最好地做到这一点。对于上面的示例,这不是问题,因为它将请求字典直接传递给https://sandbox.itunes.apple.com/verifyReceipt
@Murray Sagal 对不起,如果我说的不对,但是如果我们从沙盒环境中的沙盒帐户测试它怎么能生产?或者,如果我们从真实环境中尝试,环境会发生变化?
@wm.p1us 在生产中使用这个网址:https://buy.itunes.apple.com/verifyReceipt
如何更改 validateAppReceipt 以将其发送到自己的服务器?【参考方案2】:
我假设您知道如何执行 InApp 购买。
交易完成后,我们需要验证收据。
- (void)completeTransaction:(SKPaymentTransaction *)transaction
NSLog(@"completeTransaction...");
[appDelegate setLoadingText:VALIDATING_RECEIPT_MSG];
[self validateReceiptForTransaction];
商品购买成功后, 需要验证。服务器为我们做这个,我们 只需要传递Apple服务器返回的Receipt数据即可。
-(void)validateReceiptForTransaction
/* Load the receipt from the app bundle. */
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
if (!receipt)
/* No local receipt -- handle the error. */
/* ... Send the receipt data to your server ... */
NSData *receipt; // Sent to the server by the device
/* Create the JSON object that describes the request */
NSError *error;
NSDictionary *requestContents = @ @"receipt-data": [receipt base64EncodedStringWithOptions:0] ;
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
options:0
error:&error];
if (!requestData)
/* ... Handle error ... */
// Create a POST request with the receipt data.
NSURL *storeURL = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"];
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:@"POST"];
[storeRequest setHTTPBody:requestData];
/* Make a connection to the iTunes Store on a background queue. */
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError)
if (connectionError)
/* ... Handle error ... */
else
NSError *error;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (!jsonResponse)
/* ... Handle error ...*/
/* ... Send a response back to the device ... */
];
响应的负载是一个 JSON 对象,包含以下键和值:
状态:
如果收据有效,则为 0,或者以下提到的错误代码之一:
对于 ios 6 风格的交易收据,状态码反映了具体交易收据的状态。
对于 iOS 7 风格的应用收据,状态码是反映应用收据整体的状态。例如,如果您发送包含过期订阅的有效应用收据,则响应为 0,因为收据作为一个整体是有效的。
收据:
用于验证的收据的 JSON 表示形式。
记住:
我们将获得状态码 21007 成功收据验证, 在沙盒环境中。
在测试环境中,使用 https://sandbox.itunes.apple.com/verifyReceipt 作为 URL。在 生产,使用https://buy.itunes.apple.com/verifyReceipt作为 网址。
您需要在 iTunes Connect 中设置一个测试用户帐户才能在沙盒环境中测试购买。
编辑 1
transactionReceipt
已弃用:首先在 iOS 7.0 中弃用
if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1)
// iOS 6.1 or earlier.
// Use SKPaymentTransaction's transactionReceipt.
else
// iOS 7 or later.
NSURL *receiptFileURL = nil;
NSBundle *bundle = [NSBundle mainBundle];
if ([bundle respondsToSelector:@selector(appStoreReceiptURL)])
// Get the transaction receipt file path location in the app bundle.
receiptFileURL = [bundle appStoreReceiptURL];
// Read in the contents of the transaction file.
else
/* Fall back to deprecated transaction receipt,
which is still available in iOS 7.
Use SKPaymentTransaction's transactionReceipt. */
【讨论】:
所以即使在开发版本中(尚未公开发布),我仍然可以获得应用收据吗? (目前我们还在调查过程中,所以我没有环境可以测试) 是的,您仍会在沙盒环境中获得验证收据。基本上,您需要在 iTunes Connect 中设置一个测试用户帐户。然后,您只需使用该凭据进行购买,您将获得收据,而无需进行任何金融交易。 参考这个link 链接说的是测试In-App purchase,但我说的是App(付费应用)收据本身? 付费应用下载收据?【参考方案3】:适用于 iOS 13
以下是在没有任何服务器代码的情况下在设备上验证收据的步骤:
在验证收据之前您需要密码。这将是 共享密钥。
如何生成:
转到 -> iTunes connect 进入“Contracts, Tax, and Banking”并点击 iOs 付费应用合同上的“Request”,然后接受合同。
访问此链接
https://appstoreconnect.apple.com
1:- 点击功能
2:- 点击应用内购买并创建您的订阅包
3:- 创建订阅成功后点击 App-Specific Shared Secret
4:- 生成应用特定的共享密钥
更新了验证应用内订阅收据的代码:
-(void) verifyReceipt
/* Load the receipt from the app bundle. */
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
if (!receipt)
/* No local receipt -- handle the error. */
/* Create the JSON object that describes the request */
NSError *error;
/* reciept data and password to be sent, password would be the Shared Secret Key from Apple Developer account for given app. */
NSDictionary *requestContents = @
@"receipt-data": [receipt base64EncodedStringWithOptions:0]
,@"password": @"2008687bb49145445457ff2b25e9bff3";
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
options:0
error:&error];
if (!requestData)
/* ... Handle error ... */
// Create a POST request with the receipt data.
NSURL *storeURL = [NSURL URLWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:@"POST"];
[storeRequest setHTTPBody:requestData];
/* Make a connection to the iTunes Store on a background queue. */
//NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:storeRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
// handle request error
if (error)
//completion(nil, error);
return;
else
NSError *error;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (!jsonResponse)
/* ... Handle error ...*/
/* ... Send a response back to the device ... */
];
[dataTask resume];
希望有帮助
谢谢
【讨论】:
【参考方案4】:如果您想在沙盒环境中测试应用内进入以进行收据验证,请考虑在沙盒中更新间隔是
1 周 3 分钟 1 个月 5 分钟 2个月10分钟 3 个月 15 分钟 6个月30分钟 1年1小时
验证收据的最佳方法是将您的服务器与苹果服务器进行通信以进行验证。
【讨论】:
以上是关于iOS 测试应用收据验证的主要内容,如果未能解决你的问题,请参考以下文章