Как вы можете получить исходную версию приложения в рабочей среде?

Недавно мы перевели платное приложение на модель «Freemium». Мы используем Bundle.main.appStoreReceiptURL для извлечения квитанции, а затем проверяем «original_application_version», чтобы узнать, загрузил ли пользователь более старую платную версию из App Store или загрузил ли он более новую бесплатную версию с нерасходуемым продуктом при покупке приложения. для перехода на полную версию.

Это отлично работает при тестировании в песочнице, но в продакшене более старые версии приложения не проверяют должным образом, что они были загружены до версии freemium.

Следующий код вызывается с productionStoreURL и квитанцией, полученной из Bundle.main.appStoreReceiptURL:

private let productionStoreURL = URL(string: "https://buy.itunes.apple.com/verifyReceipt")
private let sandboxStoreURL = URL(string: "https://sandbox.itunes.apple.com/verifyReceipt")

private func verifyIfPurchasedBeforeFreemium(_ storeURL: URL, _ receipt: Data) {
    do {
        let requestContents:Dictionary = ["receipt-data": receipt.base64EncodedString()]
        let requestData = try JSONSerialization.data(withJSONObject: requestContents, options: [])

        var storeRequest = URLRequest(url: storeURL)
        storeRequest.httpMethod = "POST"
        storeRequest.httpBody = requestData

        URLSession.shared.dataTask(with: storeRequest) { (data, response, error) in
            DispatchQueue.main.async {
                if data != nil {
                    do {
                        let jsonResponse = try JSONSerialization.jsonObject(with: data!, options: []) as! [String: Any?]

                        if let statusCode = jsonResponse["status"] as? Int {
                            if statusCode == 21007 {
                                print("Switching to test against sandbox")
                                self.verifyIfPurchasedBeforeFreemium(self.sandboxStoreURL!, receipt)
                            }
                        }

                        if let receiptResponse = jsonResponse["receipt"] as? [String: Any?], let originalVersion = receiptResponse["original_application_version"] as? String {
                            if self.isPaidVersionNumber(originalVersion) {
                                // Update to full paid version of app
                                UserDefaults.standard.set(true, forKey: upgradeKeys.isUpgraded)
                                NotificationCenter.default.post(name: .UpgradedVersionNotification, object: nil)
                            }
                        }
                    } catch {
                        print("Error: " + error.localizedDescription)
                    }
                }
            }
            }.resume()
    } catch {
        print("Error: " + error.localizedDescription)
    }
}

private func isPaidVersionNumber(_ originalVersion: String) -> Bool {
    let pattern:String = "^\\d+\\.\\d+"
    do {
        let regex = try NSRegularExpression(pattern: pattern, options: [])
        let results = regex.matches(in: originalVersion, options: [], range: NSMakeRange(0, originalVersion.count))

        let original = results.map {
            Double(originalVersion[Range($0.range, in: originalVersion)!])
        }

        if original.count > 0, original[0]! < firstFreemiumVersion {
            print("App purchased prior to Freemium model")
            return true
        }
    } catch {
        print("Paid Version RegEx Error.")
    }
    return false
}

Первая бесплатная версия — 3.2, это наша текущая сборка. Все предыдущие сборки были 3.1.6 или ранее.

Производственный URL-адрес не должен быть проблемой, иначе он не вернет код состояния 21007, чтобы запустить проверку песочницы для нас. Однако устранение неполадок особенно сложно, поскольку мы не можем протестировать сам производственный URL-адрес Apple.

Кто-нибудь знает, почему это будет работать в песочнице, но не в производстве?


person Brandogs    schedule 31.12.2018    source источник
comment
Не ответ на конкретную проблему, о которой вы спрашиваете, но я настоятельно рекомендую пересмотреть использование UserDefaults, чтобы отслеживать, должны ли быть доступны платные функции. Существуют инструменты, упрощающие изменение пользовательских значений по умолчанию даже на устройствах без взлома. В этой статье подробно рассказывается, как можно использовать пользовательские настройки по умолчанию.   -  person Jamie Edge    schedule 31.12.2018
comment
@JamieEdge Спасибо за совет! Я обязательно изменю это. Разделяет ли CoreData те же риски, что и UserDefaults? Кажется чрезмерным проверять квитанцию ​​каждый раз, когда пользователь открывает.   -  person Brandogs    schedule 01.01.2019
comment
Да, хранение данных в файловой системе (например, в базовой базе данных SQLite, которая будет использоваться с Core Data) почти всегда сопряжено с таким риском, поскольку значения хранятся в каталоге песочницы приложения, который можно просматривать и изменять (UserDefaults использует plist в песочнице). Связку для ключей сложнее изменить на устройстве без взлома - значения могут быть извлечены из резервной копии, однако я не знаю какого-либо простого способа изменить значения (хотя это вполне возможно).   -  person Jamie Edge    schedule 01.01.2019


Ответы (1)


Похоже, с получением квитанции вообще не было проблем.

Некоторые из старых значений для original_application_version были отформатированы неправильно, что не позволило нам получить версию приложения для сравнения.

person Brandogs    schedule 05.01.2019
comment
Можете ли вы сказать что-нибудь о том, как они были отформатированы? Сейчас я проверяю original_application_version в производственном приложении, определяя, содержит ли строка точки (точки), но ни один пользователь не получает правильный результат. - person Mr. Zystem; 12.02.2019
comment
Нет никаких гарантий относительно того, как он будет отформатирован, поскольку original_application_version использует поле Build, которое разработчики могут установить по своему усмотрению. Однако вы можете проверить, какими были эти значения в прошлом, войдя в App Store Connect, открыв свое приложение, а затем открыв вкладку «Активность». Разверните каждую версию и просмотрите значения, перечисленные в столбце Сборка. Это значения, которые возвращаются для original_application_version для данной сборки. - person Brandogs; 13.02.2019
comment
спасибо, к сожалению, моему приложению 10 лет, и я не могу увидеть все старые версии в AppStoreConnect. Так что, думаю, мне следовало лучше документировать номера моих сборок :) - person Mr. Zystem; 14.02.2019
comment
@Mr.Zystem Если вы используете Git или другую систему контроля версий, вы можете просмотреть историю файла plist.info. Значение CFBundleVersion — это номер сборки в XCode и значение, возвращаемое как original_application_version. - person Brandogs; 18.02.2019
comment
@Makalele Проблема оказалась в том, как наша команда версионировала ранее выпущенные сборки, и в наших неверных предположениях о том, что возвращал original_application_version. Изменения для устранения проблемы были решены путем изменения регулярного выражения, но это будет специфично для каждого отдельного проекта на основе их предыдущего форматирования версии, используемой во время этих старых выпусков. В итоге делить особо нечего. Приведенный выше код должен технически работать, если предположить, что предыдущее соглашение о версиях соответствует регулярному выражению. - person Brandogs; 04.12.2019
comment
@Makalele Однако обратите внимание на риски безопасности, поднятые в комментариях к исходному вопросу об использовании основных данных и пользовательских значений по умолчанию. - person Brandogs; 04.12.2019