【发布时间】: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 文件)。钥匙串在非越狱设备上更难修改 - 值可能可以从备份中提取,但我不知道有任何简单的方法来修改值(但它很有可能)。
标签: ios swift receipt-validation