Hello, I’m trying to change the business model of my app to in-app subscriptions. My goal is to ensure that previous users who paid for the app have access to all premium content seamlessly, without even noticing any changes.
I’ve tried using RevenueCat for this, but I’m not entirely sure it’s working as expected. I would like to use RevenueCat to manage subscriptions, so I’m attempting a hybrid model. On the first launch of the updated app, the plan is to validate the app receipts, extract the originalAppVersion, and store it in a variable. If the original version is lower than the latest paid version, the isPremium variable is set to true, and this status propagates throughout the app. For users with versions equal to or higher than the latest paid version, RevenueCat will handle the subscription status—checking if a subscription is active and determining whether to display the paywall for premium features.
In a sandbox environment, it seems to work fine, but I’ve often encountered situations where the receipt doesn’t exist. I haven’t found a way to test this behavior properly in production. For example, I uploaded the app to TestFlight, but it doesn’t validate the actual transaction for a previously purchased version of the app. Correct me if I’m wrong, but it seems TestFlight doesn’t confirm whether I installed or purchased a paid version of the app.
I need to be 100% sure that users who previously paid for the app won’t face any issues with this migration. Is there any method to verify this behavior in a production-like scenario that I might not be aware of?
I’m sharing the code here to see if you can confirm that it will work as intended or suggest any necessary adjustments.
func fetchAppReceipt(completion: @escaping (Bool) -> Void) {
// Check if the receipt URL exists
guard let receiptURL = Bundle.main.appStoreReceiptURL else {
print("Receipt URL not found.")
requestReceiptRefresh(completion: completion)
return
}
// Check if the receipt file exists at the given path
if !FileManager.default.fileExists(atPath: receiptURL.path) {
print("The receipt does not exist at the specified location. Attempting to fetch a new receipt...")
requestReceiptRefresh(completion: completion)
return
}
do {
// Read the receipt data from the file
let receiptData = try Data(contentsOf: receiptURL)
let receiptString = receiptData.base64EncodedString()
print("Receipt found and encoded in base64: \(receiptString.prefix(50))...")
completion(true)
} catch {
// Handle errors while reading the receipt
print("Error reading the receipt: \(error.localizedDescription). Attempting to fetch a new receipt...")
requestReceiptRefresh(completion: completion)
}
}
func validateAppReceipt(completion: @escaping (Bool) -> Void) {
print("Starting receipt validation...")
guard let receiptURL = Bundle.main.appStoreReceiptURL else {
print("Receipt not found on the device.")
requestReceiptRefresh(completion: completion)
completion(false)
return
}
print("Receipt found at URL: \(receiptURL.absoluteString)")
do {
let receiptData = try Data(contentsOf: receiptURL, options: .alwaysMapped)
print(receiptData)
let receiptString = receiptData.base64EncodedString(options: [])
print("Receipt encoded in base64: \(receiptString.prefix(50))...")
let request = [
"receipt-data": receiptString,
"password": "c8bc9070bf174a8a8df108ef6b8d2ae3" // Shared Secret
]
print("Request prepared for Apple's validation server.")
guard let url = URL(string: "https://buy.itunes.apple.com/verifyReceipt") else {
print("Error: Invalid URL for Apple's validation server.")
completion(false)
return
}
print("Validation URL: \(url.absoluteString)")
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.httpBody = try? JSONSerialization.data(withJSONObject: request)
URLSession.shared.dataTask(with: urlRequest) { data, response, error in
if let error = error {
print("Error sending the request: \(error.localizedDescription)")
completion(false)
return
}
guard let data = data else {
print("No response received from Apple's server.")
completion(false)
return
}
print("Response received from Apple's server.")
do {
if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
print("Response JSON: \(json)")
// Verify original_application_version
if let receipt = json["receipt"] as? [String: Any],
let appVersion = receipt["original_application_version"] as? String {
print("Original application version found: \(appVersion)")
// Save the version in @AppStorage
savedOriginalVersion = appVersion
print("Original version saved in AppStorage: \(appVersion)")
if let appVersionNumber = Double(appVersion), appVersionNumber < 1.62 {
print("Original version is less than 1.62. User considered premium.")
isFirstLaunch = true
completion(true)
} else {
print("Original version is not less than 1.62. User is not premium.")
completion(false)
}
} else {
print("Could not find the original application version in the receipt.")
completion(false)
}
} else {
print("Error parsing the response JSON.")
completion(false)
}
} catch {
print("Error processing the JSON response: \(error.localizedDescription)")
completion(false)
}
}.resume()
} catch {
print("Error reading the receipt: \(error.localizedDescription)")
requestReceiptRefresh(completion: completion)
completion(false)
}
}
Some of these functions might seem redundant, but they are intended to double-check and ensure that the user is not a previous user. Is there any way to be certain that this will work when the app is downloaded from the App Store?
Thanks in advance!