Как я писал в комментариях, я не уверен, что это правильный процесс, но для приложения, которое я сделал, я проверяю квитанцию со следующим кодом:
В случае возникновения сомнений обратитесь к документация, которой я также следовал.
Также важно отметить, что Apple не рекомендует проводить прямую проверку через серверы AppStore (поскольку личность не может быть проверена, и это может привести к атакам «человек посередине»)
Используйте доверенный сервер для связи с App Store. Использование собственного сервера позволяет спроектировать приложение таким образом, чтобы оно распознавало и доверяло только вашему серверу, а также обеспечивало подключение вашего сервера к серверу App Store. Невозможно построить доверенное соединение между устройством пользователя и магазином приложений напрямую, поскольку вы не контролируете ни один из концов этого соединения и, следовательно, можете быть подвержены атаке «человек посередине».
Но, если вы можете помочь вам, вот две конечные точки Apple для (отладки/производства).
#if DEBUG
private let appStoreValidationURL = URL(string: "https://sandbox.itunes.apple.com/verifyReceipt")!
#else
private let appStoreValidationURL = URL(string: "https://buy.itunes.apple.com/verifyReceipt")!
#endif
Что касается SharedSecret
вашего приложения, которое вам нужно передать вместе с квитанцией, вы можете найти полезную информацию здесь.
- Получите квитанцию.
private func loadReceipt() throws -> Data {
guard let url = Bundle.main.appStoreReceiptURL else {
throw ReceiptValidationError.noReceiptData
}
do {
let data = try Data(contentsOf: url)
return data
} catch {
print("Error loading receipt data: \(error.localizedDescription)")
throw ReceiptValidationError.noReceiptData
}
}
- Затем вы можете прочитать содержимое в формате JSON с помощью
[...]
// Handle the try. I skipped that to make easier to read
let data = try! loadReceipt()
let base64String = data.base64EncodedString(options: [])
// Encode data in JSON
let content: [String : Any] = ["receipt-data" : base64String,
"password" : sharedSecret,
"exclude-old-transactions" : true]
- Отправьте квитанцию запроса на сервер Apple для проверки.
private func validateLastReceipt(_ data: Data) {
let base64String = data.base64EncodedString(options: [])
// Encode data in JSON
let content: [String : Any] = ["receipt-data" : base64String,
"password" : sharedSecret,
"exclude-old-transactions" : false]
let json = try! JSONSerialization.data(withJSONObject: content, options: [])
// build request
let storeURL = self.appStoreValidationURL
var request = URLRequest(url: storeURL)
request.httpMethod = "POST"
request.httpBody = json
// Make request to app store
URLSession.shared.dataTask(with: request) { (data, res, error) in
guard error == nil, let data = data else {
self.delegate?.validator(self, didFinishValidateWith: error!)
return
}
do {
let decoder = JSONDecoder()
let response = try decoder.decode(ReceiptAppStoreResponse.self, from: data)
} catch {
// Handle error
}
}.resume()
}
Вот структура Decodables, которую я построил. Здесь вы найдете всю информацию, необходимую для проверки того, что пользователь купил!
private struct ReceiptAppStoreResponse: Decodable {
/// Either 0 if the receipt is valid, or one of the error codes listed in Table 2-1.
///
/// For iOS 6 style transaction receipts, the status code reflects the status of the specific transaction’s receipt.
///
/// For iOS 7 style app receipts, the status code is reflects the status of the app receipt as a whole. For example, if you send a valid app receipt that contains an expired subscription, the response is 0 because the receipt as a whole is valid.
let status: Int?
/// A JSON representation of the receipt that was sent for verification.
// let receipt: String?
/// Only returned for receipts containing auto-renewable subscriptions. For iOS 6 style transaction receipts,
/// this is the base-64 encoded receipt for the most recent renewal. For iOS 7 style app receipts, this is the latest
/// base-64 encoded app receipt.
let latestReceipt: String?
/// Only returned for receipts containing auto-renewable subscriptions. For iOS 6 style transaction receipts,
/// this is the JSON representation of the receipt for the most recent renewal. For iOS 7 style app receipts,
/// the value of this key is an array containing all in-app purchase transactions.
/// This excludes transactions for a consumable product that have been marked as finished by your app.
let latestReceiptInfo: [ReceiptInfo]?
/// Only returned for iOS 6 style transaction receipts, for an auto-renewable subscription.
/// The JSON representation of the receipt for the expired subscription.
// let latestExpiredReceiptInfo: String?
/// Only returned for iOS 7 style app receipts containing auto-renewable subscriptions.
/// In the JSON file, the value of this key is an array where each element contains the pending renewal information
/// for each auto-renewable subscription identified by the Product Identifier.
/// A pending renewal may refer to a renewal that is scheduled in the future or a renewal that failed
/// in the past for some reason.
// let pendingRenewalInfo: String?
/// Retry validation for this receipt. Only applicable to status codes 21100-21199
// let isRetryable: Bool?
enum CodingKeys: String, CodingKey {
case status
// case receipt
case latestReceipt = "latest_receipt"
case latestReceiptInfo = "latest_receipt_info"
// case latestExpiredReceiptInfo = "latest_expired_receipt_info"
// case pendingRenewalInfo = "pending_renewal_info"
// case isRetryable = "is-retryable"
}
}
struct ReceiptInfo: Decodable {
let originalTransactionID: String?
let productID: String?
let expiresDateMS: String?
let originalPurchaseDateMS: String?
let isTrialPeriod: String?
let isInIntroOfferPeriod: String?
let purchaseDateMS: String?
enum CodingKeys: String, CodingKey {
case originalTransactionID = "original_transaction_id"
case productID = "product_id"
case expiresDateMS = "expires_date_ms"
case originalPurchaseDateMS = "original_purchase_date_ms"
case isTrialPeriod = "is_trial_period"
case isInIntroOfferPeriod = "is_in_intro_offer_period"
case purchaseDateMS = "purchase_date_ms"
}
func getExpireDate() -> Date? {
let nf = NumberFormatter()
guard let expDateString = self.expiresDateMS, let expDateValue = nf.number(from: expDateString) else {
return nil
}
/// It's expressed as milliseconds since 1970!!!
let date = Date(timeIntervalSince1970: expDateValue.doubleValue / 1000)
return date
}
Надеюсь, поможет! :)
person
GrizzlyBear
schedule
04.02.2019
ASN.1 Field Type 17
, как документы Apple указывают В файле JSON значение этого ключа представляет собой массив, содержащий все квитанции о покупках в приложении на основе транзакций о покупках в приложении, присутствующих во входных данных. данные квитанции base-64 - person GrizzlyBear   schedule 04.02.2019