Не удается восстановить активную подписку с автообновлением - PullRequest
0 голосов
/ 25 апреля 2019

Большую часть времени восстановление работает нормально, но иногда нет записи о текущем активном автообновлении в 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)
}
...