51

For a basic app with nonconsumable in-app purchases, has anyone figured out best practices for using SKPaymentQueue's restoreCompletedTransactions?

Observations

I know it's recommended to always register a transaction observer to receive pending transactions that make their way back to the app, but this is a different question. It looks like restoreCompletedTransactions is something the app has to actively decide when to call to poll for all the purchases the customer has already made.

From what I can tell, the method is designed to retrieve purchases that may have been lost. For example a customer might install or move an app to a new device in such a way where the app's local records of previous payments are lost or reset.

Concerns

What's not clear to me is how to automatically detect this condition (i.e. how to decide when to poll for missing purchases) in a reliable way. I don't want to screw this up and risk denying a customer access to functionality they've already paid for.

At the same time, I don't want to call restoreCompletedTransactions every single time the app launches just to be safe and basically get back transactions I already know about 99.9% of the time. (Except for in-app purchasing, my app doesn't really require any network connectivity.)

Notes

Apple documentation clarifies that customers are not charged again for any nonconsumable purchases they have already made. If they try to re-purchase, a payment transaction is still supposedly sent to the app.

Worst-case, a customer could recover purchases this way but I'd still like to avoid walking them down a path that resembles re-purchasing something they've already paid for.

otto
  • 2,230
  • 2
  • 26
  • 26
  • 9
    I am struggling with the same problem at the moment. The problem I see is with the UI, calling restoreCompletedTransactions immediately prompts the user for their iTunes password with no context which on startup of an app is an incredibly confusing thing to do, especially for people who have not yet purchased the upgrade. I thought about checking it when the store view controller is displayed but again, it will immediately prompt for password, something that will really put first time buyers off. – Dave Verwer Sep 28 '10 at 14:23
  • 2
    Yeah, my sentiments exactly. Automatically prompting users for their account password the first time they run the app or enter the store page was something I really wanted to avoid. I ended up going with a "Restore Purchases" button instead which seems to be working fine. I haven't received any customer complaints or confusion about it so far. – otto Sep 28 '10 at 17:17

3 Answers3

37

Update (June 2022)

Apple has given a name to this subject and devoted an entire WWDC session to it.

(If the link eventually goes stale, it was referring to session 110404 from WWDC 2022.)

The session is 20 minutes, covers a considerable amount content, and is highly recommended. There are a lot of details to navigate but I'll offer two highlights.

  • It is possible for the first launch of a freshly installed app to automatically account for pre-existing purchases without prompting the user for an Apple ID sign-in or confirmation. (restoreCompletedTransactions is not used for this.)
  • Apple still requires a "Restore Purchases" button that a user can fall back to and use if needed.

The only time your app needs to call restoreCompletedTransactions is when a user has tapped a "Restore Purchases" button. (This has pretty much been our advice for this post since the beginning.)


Previous Answer (June 2019)

Apple's documentation on this topic was updated in 2018 and is quite comprehensive. Many of its recommendations are consistent with what we ended up figuring out here. The biggest development since this question was first posted in 2009 is the App Store receipt in iOS 7.

In case the link goes stale at some point in the future, I'll quote some of the documentation here.

Restoring Purchased Products

Users restore transactions to maintain access to content they’ve already purchased. For example, when they upgrade to a new phone, they don’t lose all of the items they purchased on the old phone. Include some mechanism in your app to let the user restore their purchases, such as a Restore Purchases button. Restoring purchases prompts for the user’s App Store credentials, which interrupts the flow of your app: because of this, don’t automatically restore purchases, especially not every time your app is launched.

In most cases, all your app needs to do is refresh its receipt and deliver the products in its receipt. The refreshed receipt contains a record of the user’s purchases in this app, on this device or any other device. However, some apps need to take an alternate approach for one of the following reasons:

  • If you use Apple-hosted content, restoring completed transactions gives your app the transaction objects it uses to download the content.
  • If you need to support versions of iOS earlier than iOS 7, where the app receipt isn’t available, restore completed transactions instead.
  • If your app uses non-renewing subscriptions, your app is responsible for the restoration process.

Refreshing the receipt asks the App Store for the latest copy of the receipt. Refreshing a receipt does not create any new transactions. Although you should avoid refreshing multiple times in a row, this action would have same result as refreshing it just once.

Restoring completed transactions creates a new transaction for every completed transaction the user made, essentially replaying history for your transaction queue observer. While transactions are being restored, your app maintains its own state to keep track of why it’s restoring completed transactions and how it needs to handle them. Restoring multiple times creates multiple restored transactions for each completed transaction.


Previous Answer (2009-2012)

After writing out the question and thinking about it, I came up with a couple solutions.

Automatic (Not Recommended)

One option is to record in user defaults whether restoreCompletedTransactions has been called (and successfully completed) yet in the app. If not, the app calls it once on start-up. Since this flag could be stored in the same place as the nonconsumable payments, if user defaults get wiped later on then the restore method would get called again when the app starts.

This way, if an existing costumer is somehow doing a fresh install of the app they still get their purchases restored automatically. If they are a new customer that has never launched the app before, then the restore operation returns nothing.

In either case, restoreCompletedTransactions is only called once instead of at every launch.

Manual (Recommended)

Another option is to offer the customer a "Restore Purchases" button somewhere, hook it up to restoreCompletedTransactions and let them decide if and when it might be needed.

(The comments below go into why a manual restore is probably better than attempting to do it automatically.)

otto
  • 2,230
  • 2
  • 26
  • 26
  • The check for flag on startup and automatically restore is pretty witty. I ended up implmenting a button as did Ramp Champ. But then both applications are downloading large files when purchases are made. – Carl Coryell-Martin Nov 21 '09 at 02:09
  • 12
    Yeah. I'm leaning back towards having a restore button as well. For an automatic restore, I forgot to consider something obvious - that the user can prompted for their account password. That's the last thing I'd want to throw in the user's face the very first time the run the app. – otto Nov 24 '09 at 17:33
  • 1
    The button thing is the best option IMHO. It's what every app I see is doing, too. – ySgPjx Apr 05 '11 at 09:28
  • Calling RestoreTransactions will show the iTunes authentication dialog. This means, a new user will be confused as to why the app is asking for his iTunes password. – Mugunth Jul 29 '11 at 06:38
  • 3
    Mugunth Kumar: I think this has recently changed. In earlier versions of iOS the login and password used to downloading the app would persist for 15 minutes. That means, running the app for the first time within 15 minutes of downloading it would allow the `restoreCompletedTransactions` call to go through without any prompt. After apparent abuse of this feature, Apple changed the behaviour to always require a separate authentication for in-app purchases (which is what you're seeing). With this new approach I see no way of automatically calling `restoreCompletedTransactions` without prompt. – chris Aug 18 '11 at 05:16
  • I want to only display a "Restore Purchases" button if needed. Right now it looks like the only way to check to see if in-app purchases need to be restored is by calling 'restoreCompletedTransactions' which requires a password. Is there anyway to check first? – whatchamacallit Jun 25 '12 at 23:38
  • 3
    I had an app get rejected for NOT having a restore purchases button. I think that button HAS to be there from the get go. – badweasel Oct 24 '13 at 20:12
16

Don't forget that one Apple ID can span multiple devices. So maintaining a flag on one one device (say, the user's iPhone) that tells you whether or not you've done a restore will not allow you to detect if the customer has made a purchase on another device (say his iPad) that needs to be restored onto the iPhone. So having a manual method of launching a restore is ALSO necessary, even if you have an automatic method.

To make matters worse, I haven't figured out yet how you're notified when REFUNDS of IAPs are made. I suspect the restore process will simply return a list of the non-refunded transactions. So a) you need to DELETE your record of the user's IAP's when you do your restore in case refunded products are simply not reported during restore, and b) you need to periodically do a restore automatically in order to pick up refunds.

This all highlights the problem with Apple's IAP -- it's poorly conceived and inadequately documented -- and now it's REQUIRED for content providers like ebook readers who already have perfectly functioning web-based stores already working in their apps (like Kindle).

Craig
  • 3,253
  • 5
  • 29
  • 43
  • 3
    This is an excellent point and must be why a 'restore purchases' button is apparently required, on pain of rejection from the app store, if you use non-consumables. – Reuben Scratton Jul 02 '12 at 21:18
5

Whenever you have a non-consumable in-app purchase, for a example a new Race Track, you need to implement a 'Restore' button somewhere in the app so the user can restore their purchase if they change devices or delete the app. This is mandatory and I've had Apple reject an app before for not implementing the Restore button.

Mark Bridges
  • 8,228
  • 4
  • 50
  • 65