Moving from Paid Upfront to Freemium: Logistics

If you’re thinking about switching your app’s business model from paid upfront to freemium and, like me, have no experience working with servers, I’m here to tell you that local receipt validation isn’t as horrible as it seems.

If you’re unconcerned about piracy and simply want to check to see which version of your app was originally purchased, I highly recommend following this tutorial by Andrew Bancroft: Local Receipt Validation for iOS in Swift From Start to Finish. What I did was skim through each step of the tutorial first to see what was involved. Then, I grabbed Andrew’s code from GitHub and read more carefully through the tutorial, copying files from his project to mine as needed.

Andrew’s tutorial doesn’t go into detail about how to check for original app version, which is why I’m writing this. Hopefully someone will find it useful!

Every time Snapthread’s main view controller loads, it runs a function called checkIAPStatus(). Here’s what that function does, in pseudocode:

if the “purchasedPremium” UserDefault has been set, don’t do anything because everything is already unlocked

else if a UserDefault that I set in the previous release of Snapthread called “originallyPurchasedPaidVersion” is true, unlock everything and set the “purchasedPremium” default (this covers users who paid upfront and have used Snapthread recently enough to have had the default set)

else retrieve and validate the receipt using the ReceiptValidator class created in the tutorial (it returns a ParsedReceipt struct if successful) and examine it for originally purchased app version

NOTE: One of the most important things to remember is that the receipt doesn’t list the original App Store version number that was purchased (such as 1.0, 1.1, etc.). Instead it lists the original build number. So you’ll have to note the final build number of your paid upfront version and check against that.

When you ask your ReceiptValidator to validate a receipt, it returns an enum that may or may not have an associated value (either .success, with a ParsedReceipt struct, or .error). So you can do a switch statement on the result, and do something like case .success(let receipt): to grab a reference to the associated ParsedReceipt so you can look at it.

The originalAppVersion property of the ParsedReceipt struct is a String, so you’ll want to convert it to an Int in order to do a less-than comparison.

The only real downside, in my opinion, to doing local receipt validation using Andrew’s method is that it uses OpenSSL, which requires you to disable Bitcode in your project because it doesn’t support it. Disabling Bitcode is easy, but can cause you to get a weird e-mail from the App Store after uploading a build telling you you’ve got extra symbol files, or something like that. It’s just a warning and doesn’t prevent your build from going through or anything, but I was confused by it.

So far I haven’t received any complaints from previous purchasers who can’t export long videos or are seeing a watermark, so I’m guessing I must have done something right!