We recently transitioned a purchasable app to the "freemium" model. We are using Bundle.main.appStoreReceiptURL to pull the receipt and then check the "original_application_version" to see if a user downloaded an older paid version from the App Store, or if they downloaded a newer free version with a non-consumable in app purchase to upgrade to the full version.
This works perfectly when in testing Sandbox, but in Production, older versions of the app are not properly verifying that they were downloaded prior to the freemium version.
The following code is called with productionStoreURL and the receipt obtained from 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
}
The first freemium version is 3.2, which is our current build. All previous builds were 3.1.6 or earlier.
The Production URL shouldn't be the issue, or else it wouldn't kick back the 21007 status code to trigger the Sandbox validation for us. However, troubleshooting this is particularly tricky since we can't test against the Apple's Production URL itself.
Does anyone have any insight as to why this would work in Sandbox but not Production?
It looks like it was not a problem with obtaining the receipt at all.
Some of the older values for original_application_version
were not formatted correctly, preventing us from obtaining the app version to compare against.