Как я уже писал в комментариях, я не уверен, что это правильный процесс, но для приложения, которое я сделал, я проверяю квитанцию со следующим кодом:
По любым сомнениям, пожалуйста, обращайтесьна документы Я также следовал.
Важно также отметить, что Apple не рекомендует напрямую проверять через серверы AppStore (так как идентификация не может быть проверена, и это может привести катаки в середине)
Используйте доверенный сервер для связи с App Store.Использование вашего собственного сервера позволяет вам разрабатывать приложение, чтобы распознавать и доверять только вашему серверу, а также обеспечивает подключение вашего сервера к серверу 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
}
Надеюсь, это поможет!:)