Большинство ответов можно найти на здесь в документации Apple. Но есть пробелы, и в коде objective-c используются устаревшие методы.
Этот код Swift 3 показывает, как получить квитанцию о приложении и отправить ее в магазин приложений для проверки. Вы обязательно должны подтвердить квитанцию о приложении в магазине приложений, прежде чем сохранять нужные данные. Преимущество запроса магазина приложений на проверку заключается в том, что он отвечает данными, которые вы можете легко сериализовать в 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/
}
Эта часть проверяет квитанцию приложения. Это не местная проверка. См. Примечания 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;
}
person
Murray Sagal
schedule
22.09.2016