Большую часть времени восстановление работает нормально, но иногда нет записи о текущем активном автообновлении в latest_receipt_info
(при попытке купить активную подписку Iполучить сообщение, что я уже подписан).Кто-нибудь испытывал это раньше?
А во-вторых, не могли бы вы, ребята, пересмотреть мой код?Конечно, я не должен проверять квитанцию напрямую на сервере App Store
, но я считаю, что это не проблема.Я сомневаюсь, что код неисправен (как я уже писал выше, в квитанции не было последней записи), но я могу ошибаться.Возможно, я упускаю что-то важное.
PS Сначала я подозревал, что я могу неправильно сравнивать даты (после извлечения последней даты истечения срока из квитанции я сравнил ее с датой () таким образом:
if expirationDate > Date() {
// There is an active subscription
}
Я подумал, что, возможно, мне нужно преобразовать Date()
и expirationDate в GMT+0
, а затем сравнить, но обе даты выглядят как GMT + 0.
Вот код:
Наблюдатель транзакции
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
var purchased = false
var restored = false
for transaction in transactions {
switch transaction.transactionState {
case .purchased:
purchased = true
queue.finishTransaction(transaction)
case .restored:
restored = true
queue.finishTransaction(transaction)
case .deferred:
queue.finishTransaction(transaction)
case .failed:
purchaseProductCompletionHandler?(IAPError.purchaseFailed)
queue.finishTransaction(transaction)
}
}
if purchased {
processReceipt(completion: { error, subscriptionExpirationDate, appIsPurchased in
if let error = error {
self.purchaseProductCompletionHandler?(error)
} else {
self.purchaseProductCompletionHandler?(nil)
}
})
} else if restored {
processReceipt(completion: { error, subscriptionExpirationDate, appIsPurchased in
self.restorePurchasesCompletionHandler?(error, subscriptionExpirationDate, appIsPurchased)
})
}
}
Код, который подтверждает чек и анализирует его
private func processReceipt(completion: @escaping (_ error: Error?, _ subscriptionExpirationDate: Date?, _ appIsPurchased: Bool?) -> Void) {
guard let receiptUrl = Bundle.main.appStoreReceiptURL, let receiptData = try? Data(contentsOf: receiptUrl) else {
completion(IAPError.receiptNotFound, nil, nil)
return
}
makeReceiptValidationRequest(receiptData: receiptData, url: receiptValidationProductionUrl, completion: { error, data in
if let error = error {
completion(error, nil, nil)
return
}
guard let data = data else {
completion(IAPError.receiptValidationRequestCompletedWithoutErrorAndData, nil, nil)
return
}
guard let receiptStatus = try? self.extractReceiptStatus(data) else {
completion(IAPError.jsonProcessingFailed, nil, nil)
return
}
if receiptStatus == 21007 {
self.makeReceiptValidationRequest(receiptData: receiptData, url: self.receiptValidationSandboxUrl, completion: { error, data in
if let error = error {
completion(error, nil, nil)
return
}
guard let data = data else {
completion(IAPError.receiptValidationRequestCompletedWithoutErrorAndData, nil, nil)
return
}
let purchasesInfo = self.processReceiptValidationRequestResponse(data: data)
completion(purchasesInfo.error, purchasesInfo.subscriptionExpirationDate, purchasesInfo.appIsPurchased)
})
} else {
let purchasesInfo = self.processReceiptValidationRequestResponse(data: data)
completion(purchasesInfo.error, purchasesInfo.subscriptionExpirationDate, purchasesInfo.appIsPurchased)
}
})
}
makeReceiptValidationRequest ()
private func makeReceiptValidationRequest(receiptData: Data, url: URL, completion: @escaping (_ error: Error?, _ data: Data?) -> Void) {
let payload = ["receipt-data": receiptData.base64EncodedString().toJSON(),
"password": secretKey.toJSON()]
guard let serializedPayload = try? JSON.dictionary(payload).serialize() else {
completion(IAPError.receiptValidationRequestCreationFailed, nil)
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = serializedPayload
URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in
if let error = error {
completion(error, nil)
} else if let data = data {
completion(nil, data)
} else {
completion(IAPError.receiptValidationRequestCompletedWithoutErrorAndData, nil)
}
}).resume()
}
processReceiptValidationRequestResponse ()
private func processReceiptValidationRequestResponse(data: Data) -> (error: Error?, subscriptionExpirationDate: Date?, appIsPurchased: Bool?) {
guard let purchasesInfo = try? extractPurchasesInfo(data) else {
return (IAPError.jsonProcessingFailed, nil, nil)
}
if purchasesInfo.receiptStatus != 0 {
return (IAPError.invalidReceipt, nil, nil)
}
if let subscriptionExpirationDate = purchasesInfo.subscriptionExpirationDate, subscriptionExpirationDate > Date() {
UserDefaults.standard.set(subscriptionExpirationDate, forKey: PurchasesInfoKey.subscriptionExpirationDate)
}
UserDefaults.standard.set(purchasesInfo.freeTrialWasUsedEarlier, forKey: PurchasesInfoKey.freeTrialWasUsedEarlier)
UserDefaults.standard.set(purchasesInfo.appIsPurchased, forKey: PurchasesInfoKey.appIsPurchased)
return (nil, purchasesInfo.subscriptionExpirationDate, purchasesInfo.appIsPurchased)
}
extractPurchasesInfo ()
private func extractPurchasesInfo(_ data: Data) throws -> (receiptStatus: Int, subscriptionExpirationDate: Date?, freeTrialWasUsedEarlier: Bool?, appIsPurchased: Bool?) {
let jsonData = try JSON(data: data)
let receiptStatus = try jsonData.getInt(at: "status")
if receiptStatus != 0 {
return (receiptStatus, nil, nil, nil)
}
var subscriptionExpirationDate: Date?
var freeTrialWasUsedEarlier = false
var appIsPurchased = false
if let receipt = jsonData["receipt"], let inApps = try? receipt.getArray(at: "in_app") {
for inApp in inApps {
let productId = try? inApp.getString(at: "product_id")
if productId == ProductId.oneTimePurchase.rawValue {
appIsPurchased = true
break
}
}
}
if let latestReceiptInfo = try? jsonData.getArray(at: "latest_receipt_info") {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV"
for receipt in latestReceiptInfo {
guard let productId = try? receipt.getString(at: "product_id") else {
continue
}
if let isTrialPeriod = try? receipt.getString(at: "is_trial_period"), isTrialPeriod == "true", [, ProductId.oneMonthWithFreeTrial.rawValue, ProductId.oneYearWithFreeTrial.rawValue].contains(productId) {
freeTrialWasUsedEarlier = true
} else if productId == ProductId.oneTimePurchase.rawValue {
appIsPurchased = true
}
guard let expiresDateString = try? receipt.getString(at: "expires_date"), let expiresDate = dateFormatter.date(from: expiresDateString) else {
continue
}
if subscriptionExpirationDate == nil || expiresDate > subscriptionExpirationDate! {
subscriptionExpirationDate = expiresDate
}
}
}
return (receiptStatus, subscriptionExpirationDate, freeTrialWasUsedEarlier, appIsPurchased)
}