59

I currently have a paid app in the store. Apple have not allowed a 'lite' version to be submitted as well, so I have no choice but to update the current paid version to a freemium (with in app purchase) model. I have the problem of not loosing functionality for v1 users that have purchased the app the first time round.

Is there any way to determine if an application have been updated from a previously installed version so I can unlock the paid parts of the app?

Two similar questions (from a few months ago):

Transition an existing paid for app to free version with In App Purchase

iPhone + upgrade existing paid application on app store to free application with In App purchase + what about the customers who have already purchased the paid application

Community
  • 1
  • 1
fringley
  • 591
  • 1
  • 5
  • 3
  • possible duplicate of [Transition an existing paid for app to free version with In App Purchase](http://stackoverflow.com/questions/1575965/transition-an-existing-paid-for-app-to-free-version-with-in-app-purchase) – Brad Larson Sep 17 '10 at 15:16
  • As it stands, this is an exact duplicate of the first question. What are you asking that is different? The answers provided there should apply in your case. – Brad Larson Sep 17 '10 at 15:17
  • 1
    I appreciate the question is the same, but the answers given were around a year ago. The SDK has moved on a few times since then. That and the suggestions given don't really answer the problem. – fringley Sep 17 '10 at 16:21
  • How did you solve this problem!? – AXE Feb 05 '14 at 01:51
  • Can you post, how did you solved this problem? – rule Jun 03 '15 at 12:19

6 Answers6

34

There is now an Apple-approved way to do this on both iOS and macOS. The originally downloaded version of the app can be obtained from the receipt using the info key Original Purchased Version. You can then decide whether to unlock features if that version predates the switch to IAP.

For instance, once you have retrieved the receipt info:

NSArray *versionsSoldWithoutIAP = @[@"1.0", @"1.1", @"1.2", @"1.3"];
NSString *originalPurchasedVersion = [receiptInfoDict objectForKey:@"Original Purchased Version"];
for (NSString *version in versionsSoldWithoutIAP) {
    if ([version isEqualToString:originalPurchasedVersion]) {
        // user paid for the currently installed version
    }
}

For more info see the WWDC 13 video Using Receipts to Protect Your Digital Sales. At 5:40 the presenter comments: "I think the most exciting thing that's in the receipt this year, especially for you guys if you have a paid app in the store is that we've included information in the receipt that's going to let you do a transition from being a paid app to being a free app with in-app purchases without leaving behind all the customers that have already paid for your app."

spinacher
  • 549
  • 6
  • 10
  • 1
    I believe the key name is `original_application_version` instead of `Original Purchased Version`. – iStar Sep 26 '17 at 06:00
  • 1
    for those who have no idea how to obtain receiptInfoDict: it's being returned as the remote validation response: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html – Roman86 May 28 '18 at 13:00
  • amazing, that caused my team such a headache before we start to search for it, thx a lot! – patreu22 Mar 05 '19 at 00:44
  • Could someone please show a more complete version of this? – Petrus Theron May 26 '19 at 11:42
  • For me this returns an empty response. What should I do? (hint: I have no IAP in place, I just want to get the orignal purchased version from app store). – user826955 Feb 02 '21 at 14:57
  • How about Android version of this? Is this possible to do the same transition for Google Play users? – Marakoss Jan 28 '22 at 02:39
21

With iOS7, iOS app can verify app store receipt, which contains app download date. By using this donwload date, you could determine if a customer is previously purchased or not

Arnold Tang
  • 321
  • 2
  • 4
  • 4
    To expand upon this, `NSBundle` has a property - `appStoreReceiptURL`, which may (but isn't guaranteed to) return a receipt history for the app. Although I don't think it contains a download date for the app itself (according to the Receipt Fields documentation, it's a field on IAP receipts, not the app receipt), you can get the version number that was originally purchased and use this to check. Feel free to correct me, I haven't tested this out yet. – Xono Oct 30 '13 at 23:41
  • This could be the ideal solution if it works. Does anyone know if Android has a similar property? – Carlos P Nov 04 '13 at 09:13
  • @Xono - `appStoreReceiptURL` returns the receipt associated with the purchase of your app, not the history of IAP... https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSBundle_Class/Reference/Reference.html#//apple_ref/occ/instm/NSBundle/appStoreReceiptURL – Maciek Czarnik Feb 15 '14 at 01:56
  • Is this still the way to go? Is the "app download date" just the date that they installed the app? If it's only the download/install date, this would not be effective because what if they deleted it and reinstalled at a later time. Or is it actually the date they **purchased** the app, which would make more sense? – Jet59black May 21 '16 at 15:14
11

First, I just want to say I personally think the freemium model is great. It has worked out very well for many developers. People love to download free apps, and will do it on a whim, but pay much more attention to an app before spending $0.99 (Which is due to the effect of free - for more info on that, check out Dan Ariely's book Predictably Irrational)

For more info on freemium, google it - There have been tons of articles written about the success of it.


Ok, back to the actual question:

Theres a couple ways you can handle a situtation like this, although the unfortunate matter here is none of them are fool proof.

  • The best solution would probably be for your users to have accounts. Without knowing the specifics of your app, I can't say whether or not user accounts are appropiate for your app. User accounts stored on your server have many additional benefits, including user management, and tracking what purchases a user has made. This will allow users who delete the app, and then re-install it, or get a new device, to maintain their purchased content. Furher, whenever you use in-app purchase, you should validate the purchase on your own server (or with Apple), which a server based user manegment system can all do. If your totally in over your head with creating your own user management server, check out Parse. Its dead simple to create an amazing backend server (for basically free)
  • iCloud Key/Value type of system. I'm not very familiar with how this would work - so I'll move on.
  • Another, not nearly as fool proof solution (but much quicker/easier to implement) is to use NSUserDefaults. You can store an object when the user makes a purchase, or with the date a user installs your app. Then if you issue an update converting your app to freemium. Then in the new update, check which purchases the user has made or the date they installed it, and react accordingly. For info on how to do that with NSUserDefaults, check out my answer to another question on implementing that: NSUserDefaults and app versions. But this solution does present the following pitfalls:

  • If the user deletes your app, the NSUserDefaults are lost forever

  • If the user didn't install the update setting up the NSUserDefault system, but then installed the update with the new freemium model, the app would treat them as if they hadn't purchased the content.

In summery, this is a difficult question, with not a lot of easy/perfect options.

Anyway,

Hope that helped!

Community
  • 1
  • 1
Andrew
  • 3,874
  • 5
  • 39
  • 67
  • 3
    This solution doesn't protect you from the fact that an original paying user may delete and then re-install the app. In such case the user defaults will be deleted and then after re-install of the freemium version he/she will lose the extra features. So this solution, which partially works, can be improved by saving the UDID of current customers in a server, thus giving an extra protection. Of course you are still not protected from users deleting the app and doing a complete reset of their iPhone. But it's a minor case. – viggio24 Dec 11 '11 at 08:05
  • True, but those seem like extreme cases. However, if you would rather be better protected, more power to you. let me know in this thread how it work out – Andrew Dec 11 '11 at 15:14
  • 3
    Better store it in the iCloud Key/Value Store if possible, instead of user defaults since they get deleted when a user removes the app! –  Feb 14 '12 at 16:01
  • When this question was active, iCloud wasn't publicly available – Andrew Feb 15 '12 at 02:36
  • 1
    This could run afoul of the iOS developer terms and conditions which state "3.3.3 Without Apples prior written approval or as permitted under Section 3.3.23 (In App Purchase API), an Application may not provide, unlock or enable additional features or functionality through distribution mechanisms other than the App Store or VPP/B2B Program Site." Anyone know if Apple cares about this scenario? I had an app denied that featured external unlocking (before I knew this), but this case seems like it might be acceptable. – Nicholas Dec 20 '12 at 21:39
  • I've never had a problem with this. Although, rereading this answer, I'm realizing there are better solutions nowadays. But this post is almost two years old. – Andrew Dec 21 '12 at 01:27
  • Good to know, thanks. I'd love to hear about some better solutions... I've been glued to Google for hours looking for ways to get around this. – Nicholas Dec 21 '12 at 01:39
  • Thanks Andrew! Looks like it's down to these options: NSUserDefaults, iCloud key/value, and homegrown user accounts. – Nicholas Dec 25 '12 at 05:52
2

I'm dealing with the same thing and came up with the following idea: Create the freemium version under a new name and app ID. Keep the existing paid app in the app store, but raise the price to something absurd and clearly state in the description that the app is there to maintain support for existing users and that new users should try the freemium version instead.

Existing paid users won't lose support for their existing app and can delete and install any time it without re-purchasing.

You won't have to keep updating the old paid app, either. Just keep it in the app store.

The downside is that existing paid users will not be able to migrate smoothly to the freemium version to get any extra features you add in the future without re-paying for what they already have.

Still trying to decide if this will work for me but it could be a good option for others. Comments appreciated.

Nicholas
  • 447
  • 1
  • 4
  • 18
  • Did you try this? What did users think? Have the same problem. Trying to figure out how to proceed. – Fraggle Mar 17 '14 at 16:44
  • I did not try it, sorry. I ended up updating the paid app to set a flag in people's iCloud accounts stating that they have the paid app. Then I used a force-upgrade flag I had in the app that disables the app. Customers could no longer use the app and were forced to upgrade to the new version that sets the flag. It caused a little confusion but I made sure the wording was smooth so people knew what was happening. That's as far as I've gotten with it. – Nicholas Mar 20 '14 at 13:58
  • This wouldn't work with current review guidlines. Apple doesn't allows two versions of the same app anymore. – kelin Jul 24 '19 at 19:53
  • Absurdly high prices will not work: Apple Review Guidelines, section "3. Business": "...we won’t distribute apps and in-app purchase items that are clear rip-offs.". – André Gasser Sep 06 '19 at 10:35
2

I've been thinking about this problem for some time now. I have a substantial amount of customers that paid for my (in App Store terms) high-price niche-App and I'd hate having to tell them to re-purchase as I plan to migrate to an In-App Purchase model.

The idea I came up with (and I'll ask Apple support whether it's legal) is to phase out the current paid App but ship a last update for it that allows "unlocking" the In-App purchases of the new App based on the In-App model. I was thinking about a challenge response scheme:

  • User has installed paid App on his device
  • User installs new In-App App and opens it. The new App detects the paid version and offers to unlock the In-App purchases (on this device only of course and as long as the App isn't deleted)
  • The new App generates a nonce, signs it and calls the old App with it via an URL Scheme
  • The old App decrypts the nonce, adds +1 one to it and signs it again. Calls back to the new App via URL scheme
  • The new App validates the nonce and unlocks the features

The scheme can be easily implemented using a pre-shared key. It's of course a weakness on jail-broken devices, but then every App storing In-App receipts has those problems.

Johannes Rudolph
  • 35,298
  • 14
  • 114
  • 172
  • Note that this may be against section 11.1 of the review guidelines: https://developer.apple.com/appstore/resources/approval/guidelines.html#purchasing-currencies I did contact the App Review team to what they recommend to upgrade the App. – Johannes Rudolph Jun 06 '13 at 06:25
  • Please share what they answered! – AXE Feb 05 '14 at 01:47
  • 1
    I was sent a link to a couple of resoucres with a comment saying 'We recognize this doesn't completely address your request but we hope you find these resources helpful.' I ended up not trying to sneak this approach through the App Review process. Turns out you can't do promotion codes for In App purchases, so I ended up maintaining a "Pro" and "lite+IAP" version.... – Johannes Rudolph Feb 05 '14 at 13:05
2

You can check the 'original_application_version' of the receipt. All iOS downloaded from the appStore have a receipt even if it is a free app.

TPInAppReceipt is a simple swift library that can help you with this.

import TPInAppReceipt

do {
  /// Initialize receipt
  let receipt = try InAppReceipt.localReceipt() 

  let originalAppVersion = receipt.originalAppVersion
  let buildSoldWithoutIAP = 22

  let originalAppVersionInt = Int(originalAppVersion) ?? 23
  if originalAppVersionInt <= buildSoldWithoutIAP {
                
      // unlock all features
      UserDefaults.standard.set(true, forKey: "isPaid")
  }

} catch {
  print(error)
}

Note: The receipt.originalAppVersion returned is the build number as at the time the user first purchased the app from the appSore. Also, the receipt won't be available in the sandbox environment until you purchase or restore an inAppPurchase first.

Timchang Wuyep
  • 629
  • 7
  • 10