Update (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.)
OK. I've figured this out. It was my test account. Since I was taking the account and logging in through Settings->App Store, I was registering each with credit card and email notification. I used an old account that created before but hadn't "activated", and it worked. So, the key was to not log in through the Settings->Store - stay logged out. And then log in with a test account that was only created on iTunes Connect, but never activated.
Best Answer
IMO there are 3 things you can do to make testing non-consumables bearable:
You can have many test accounts associated to one email. Gmail for example lets you add a "plus" string to the email to create aliases for an address: so
tester+01@gmail.com
andtester+02@gmail.com
both really just go totester@gmail.com
. Probably other email hosts do the same. When you create a test account you need to introduce: first name, last name, email address, password, secret question, secret answer, date of birth, and iTunes store country. You can put exactly the same data (including password) fortester+01@gmail.com
andtester+02@gmail.com
and you will have two test accounts. Finally, in yourtester@gmail.com
inbox you will receive two verification emails from Apple to confirm both test accounts.Say that you have a non-consumable with product ID @"Extra_Levels". Instead of writing @"Extra_Levels" in all methods (requestProduct, purchaseProduct, ...), just write
PRODUCT_ID1
and at some header file put#define PRODUCT_ID1 @"Extra_Levels"
(with no semicolon!), then the preprocessor will search PRODUCT_ID1 and substitute it for @"Extra_Levels". Then creating a new non-consumable called @"Extra_Levels_01" and changing the #define will be as good as resetting the purchases for all your test users.As appsmatics pointed out, you can test the correct behavior of your code when you buy a non-consumable IAP by first using a consumable IAP (so that test user can make as many purchases as needed) to get rid of some bugs. Of course, you should also test the code with the real non-consumable IAP after that.