В StackOverflow по-прежнему появляется много вопросов о валидации App Store чеков, поэтому мы решили написать статью на эту тему в формате вопросов и ответов.
Что представляет собой App Store чек?
Это зашифрованный файл в формате PKCS#7, который содержит в себе информацию обо всех покупках в приложении. Находится в бандле приложения и его можно легко получить, вызвав: Bundle.main.appStoreReceiptURL
.
Всегда ли есть этот файл?
Если приложение было скачано из App Store, то да, всегда. А если было установлено через Xcode или Testflight, то первоначально у приложения sandbox-чек отсутствует до первой покупки или восстановления чека.
Что значит "провалидировать чек"?
Это означает расшифровать файл, получить JSON дату и сверить совершенные пользователем покупки. Сделать это можно локально или отправив запрос в Apple.
В каких случаях разработчику требуется валидация чека?
Для валидации только что совершенной покупки.
Когда у многих был джейлбрейк это было актуально: существовали утилиты для подделки чека. Сейчас эта проблема перестала быть острой, потому что джейлбрейк стал редкостью.
При восстановлении покупок.
Если пользователь переустановил ваше приложение или запустил с другого устройства, вы должны предоставить ему доступ к функционалу, за который он уже заплатил. Расшифровав App Store чек, вы сможете выяснить, была ли приобретена встроенная покупка.
При покупке авто-возобновляемых подписок.
Для определения текущего статуса подписки и даты истечения.
Какие покупки можно восстановить при валидации?
Существует 4 вида встроенных покупок:
- расходуемые (consumable purchases),
- нерасходуемые (non-consumable purchases),
- невозобновляемые подписки (non-renewing subscriptions),
- авто-возобновляемые подписки (auto-renewable Subscriptions).
Восстановить можно все, кроме расходуемых покупок. К ним можно отнести, например, монетки в вашем приложении – что-то, что можно купить сколько угодно раз. Вы должны сами сохранять текущее количество монеток у пользователя на своем сервере.
Какие способы валидации существуют?
Их три:
- локальная валидация с использованием OpenSSL,
- валидация по запросу в Apple прямо с iOS устройства,
- валидация по запросу в Apple с использованием вашего сервера.
Какой способ валидации лучше?
Локальная валидация сложна и требует много времени и усилий на реализацию. А еще вы должны будете добавить OpenSSL библиотеку в ваш проект. В некоторых случаях придется обновлять чек.
Apple не рекомендует проверять чеки на самом iOS-устройстве. Это небезопасно: запрос можно перехватить с помощью man-in-the-middle атаки.
Лучше всего валидировать чеки на сервере. Тем более, что Apple время от времени добавляет туда новые поля, например, grace_period_expires_date
и subscription_group_identifier
. На своем сервере мы сможете быстро вносить изменения без обновления приложения. А еще предыдущие два метода валидации можно легко обмануть, просто поменяв системное время на iOS устройстве.
Для чего нужен Shared Secret?
Это специальная строка-ключ, которая необходима для расшифровки чеков с авто-возобновляемыми покупками. Apple использует Shared secret как параметр в HTTPS запросе к Apple.
Где взять Shared Secret?
Перейдите в App Store Connect, откройте приложение, перейдите во вкладку Функции, в разделе Встроенные покупки увидите кнопку Общий ключ для приложения. Сгенерируйте новый ключ, если его еще нет.
Пример кода для валидации чека
func validateReceipt(){
#if DEBUG
let urlString = "https://sandbox.itunes.apple.com/verifyReceipt"
#else
let urlString = "https://buy.itunes.apple.com/verifyReceipt"
#endif
guard let receiptURL = Bundle.main.appStoreReceiptURL, let receiptString = try? Data(contentsOf: receiptURL).base64EncodedString() , let url = URL(string: urlString) else {
return
}
let requestData : [String : Any] = ["receipt-data" : receiptString,
"password" : "YOUR_SHARED_SECRET",
"exclude-old-transactions" : false]
let httpBody = try? JSONSerialization.data(withJSONObject: requestData, options: [])
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = httpBody
URLSession.shared.dataTask(with: request) { (data, response, error) in
// convert data to Dictionary and view purchases
}.resume()
}
Это пример валидации чека на iOS. Не забудьте заменить значение YOUR_SHARED_SECRET
на ваш shared secret.
Получив data
, сконвертируйте его в Dictionary
:
DispatchQueue.main.async {
if let data = data, let jsonData = try? JSONSerialization.jsonObject(with: data, options: .allowFragments){
// your non-consumable and non-renewing subscription receipts are in `in_app` array
// your auto-renewable subscription receipts are in `latest_receipt_info` array
}
}
Пример расшифрованного App Store чека
Здесь можно посмотреть пример чека с двумя транзакциями в приложении с авто-возобновляемой подпиской.
В чем отличия между in_app
и latest_receipt_info
?
-
latest_receipt_info
содержит транзакции только авто-возобновляемых покупок. -
in_app
содержит транзакции нерасходуемых покупок и невозобновляемых подписок. Еще здесь дублируется первая транзакция вашей авто-возобновляемой подписки. Расходуемые покупки тоже появятся в массивеin_app
, но потом исчезнут, когда разработчик завершит транзакцию.
Заключение
Мы в Apphud реализовали валидацию App Store чеков для приложений с авто-возобновляемыми подписками в удобном open-source SDK. А еще Apphud помогает отслеживать статус подписки, анализировать ключевые метрики, автоматически предлагать скидки отписавшимся пользователям и многое другое. Если при работе с подписками вы испытываете боль, попробуйте наше решение бесплатно.
Автор: Renat Kurbanov