如何在生产中获得原始应用程序版本?

Posted

技术标签:

【中文标题】如何在生产中获得原始应用程序版本?【英文标题】:How can you get Original Application Version in Production? 【发布时间】:2019-05-28 04:07:07 【问题描述】:

我们最近将一款可购买的应用转变为“免费增值”模式。我们正在使用 Bundle.main.appStoreReceiptURL 来提取收据,然后检查“original_application_version”以查看用户是否从 App Store 下载了较旧的付费版本,或者他们是否下载了具有非消耗性应用内购买的较新的免费版本升级到完整版。

这在测试 Sandbox 时非常有效,但在生产环境中,旧版本的应用无法正确验证它们是在免费增值版本之前下载的。

使用 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 状态代码来为我们触发沙盒验证。但是,解决此问题特别棘手,因为我们无法针对 Apple 的生产 URL 本身进行测试。

有没有人知道为什么这会在 Sandbox 中起作用,但在生产中不起作用?

【问题讨论】:

不是您所询问的具体问题的答案,但我强烈建议您重新考虑使用 UserDefaults 来跟踪是否应该提供付费功能。存在可以轻松修改用户默认值的工具,即使在非越狱设备上也是如此。 This article 详细说明了如何利用用户默认值。 @JamieEdge 感谢您的提示!我一定会改变这一点。 CoreData 是否与UserDefaults 具有相同的风险?每次用户打开时都验证收据似乎有点过分。 是的,在文件系统上存储数据(例如与 Core Data 一起使用的底层 SQLite 数据库)几乎总是会带来这种风险,因为值存储在应用程序的沙箱目录中,可以被查看和修改(UserDefaults 在沙箱中使用 plist 文件)。钥匙串在非越狱设备上更难修改 - 值可能可以从备份中提取,但我不知道有任何简单的方法来修改值(但它很有可能)。 【参考方案1】:

看起来获取收据根本没有问题。

original_application_version 的一些旧值格式不正确,导致我们无法获取应用版本进行比较。

【讨论】:

你能说说它们是如何格式化的吗?我现在通过检测字符串是否包含点(句号)但没有一个用户得到正确的结果来检查生产应用程序中的 original_application_version。 由于 original_application_version 使用 Build 字段,开发人员可以根据需要进行设置,因此无法保证其格式。但是,您可以通过登录 App Store Connect,打开您的应用程序,然后打开“活动”选项卡来查看这些值过去的值。展开每个版本并查看 Build 列中列出的值。这些是为给定构建的 original_application_version 返回的值。 谢谢,很遗憾,我的应用程序已有 10 年历史,我无法在 AppStoreConnect 中查看所有旧版本。所以我想我应该在记录我的内部版本号方面做得更好:) @Mr.Zystem 如果您使用 Git 或其他版本控制系统,您可以查看 plist.info 文件的历史记录。 CFBundleVersion 值是 XCode 中的内部版本号,是作为 original_application_version 返回的值。 @Makalele 问题最终是我们的团队如何对先前发布的版本进行版本化,以及我们对original_application_version 返回的内容的错误假设。解决问题的更改是通过更改正则表达式来解决的,但这将特定于每个单独的项目,具体取决于他们在旧版本时使用的版本格式。结果,真的没有什么可分享的。假设以前的版本控制符合正则表达式,上面的代码在技术上应该可以工作。

以上是关于如何在生产中获得原始应用程序版本?的主要内容,如果未能解决你的问题,请参考以下文章

什么是 android 中的生产密钥库,如何获得生产密钥库的路径?

如何在生产和开发环境中始终如一地获得 ASP.NET 5 DNX 项目的应用程序基础路径?

使用 C++ 在 Windows 中获取 OSVersion

全局标志恢复为 C 中的原始逻辑

如何获取 Chromecast Sender SDK 的生产版本以包含在我的应用程序中

如何确定购买该应用(不是应用内购买)的原始版本或日期