Visit my site @ mugunthkumar.com


iPhone Tutorial – In-App Purchases

October 18th, 2009 by Mugunth Kumar

Last week, Apple announced that in-app purchases will be available for free apps as well. This could probably free developers from creating “lite” and “pro” versions of the app and allow developers to “unlock” features inside the app and create business models that the AppStore haven’t seen. This model could be a great boon for developers like us to upsell our apps (provided they are of good quality) and to reduce piracy. In this article, we will look at how to incorporate StoreKit to do In-App purchases for your iPhone Application.

Update: Did it really free you from creating “lite” and “pro” versions? How did you manage to give away free copies of your in-app purchases for reviewers? Read my tutorial on how toenable in-app purchases for free

Apple allows three types of purchases within the app and Apple terms them as consumables, non-consumables and subscriptions.

Consumables are products that are “consumed” immediately. This means, if the purchase is made today, and the user wants to purchase it again tomorrow, he will be charged again when he attempts a purchase.

Non-Consumables are features that are purchased exactly once. Apple automatically takes care of “remembering” those purchases and allows the user to purchase them again for free, just like downloading apps you already purchased.

Subscriptions are the most complicated part, They behave like non-consumables during the subscribed period and like consumables after that. You as a developer have to ensure that anything that is subscribed by the user is available across all of his iTunes synced devices when they are purchased from one device. Hence, do not lock in-app purchases to UDIDs. This might even get your app rejected. The StoreKit, as on date, doesn’t have any built-in mechanism to do it automatically which is why subscriptions are a bit tougher to develop.

One important point to note is that, in-app purchases cannot be used to deliver product updates. Changes to the binary has to be separately submitted. However, if you are a game developer, game data, maps, levels and other “data files” are allowed for in-app purchase.

In this post, we will focus on how to prepare your app for enabling features for the “pro” version from the “lite”. Or technically, we will focus on how to bring in, consumables and non-consumables into your app. We will leave the subscriptions part to another blog post as it’s quite complicated and involves some server side programming as well.

Preparing your iTunes Connect


To start with, in-app purchases, you need to do some ground work on your iTunes Connect account. A three step process that Apple thinks every developer should know. (Unfortunately I couldn’t find any official documentation for this)

Step 1:

First is to create an App ID and enable in-app purchases for that. This App ID shouldn’t have any wild card characters or else, the in-app purchases option will be grayed. I would always recommend to use a different App ID for every application you create.

Step 2:

Create provisioning profiles (Development and Distribution) using this App ID. Again it’s a good practice to create different provisioning profiles for every app. There are many tutorials on how to create this here and Apple’s own documentation here (This link will prompt you to login).

Step 3:

You need to create product references in your iTunes account. Each individual in-app purchase should be uniquely identifiable. Apple recommends using the reverse DNS notation, something like, com.mycompany.myiproduct.ifeature Before creating product ids, you need to associate it with a existing application in AppStore. If your app is not yet live, you can create a dummy, placeholder application, fill in the metadata (which you can anyway change it later) and check “upload binary later”.

To create a new in-app purchase, open your itunes connect and choose “Manage In-App purchases”. Choose the app for which you want to setup in-app purchases and click next. You should see a screen like this.

iTunes Connect in-app Purchase

The reference name is the name that appears during the in-app purchase prompt. Any name like, Levels 10 – 50 will be fine. The Product ID should be unique. This is used for reporting as well as within your app for requesting a purchase (more on this later). You can select the type as consumable or non-consumable. When you say an in-app purchase is consumable, your users will be charged everytime they purchase it. This is perfect for a radio app that requires users to pay for listening to a song everytime. If it’s a product feature, set it as non-consumable. Non-consumable products are purchased exactly once. When the user attempts to purchase it again, it will be delivered to him for free.

Type in the other required detail in this page and click save.

Step 4:

The fourth and final step is to create test user accounts. After you program the app, you might want to test the app. You can use these accounts to login to the App Store. The purchases will be processed as if it were real but no financial transactions will take place.

This completes your iTunes connect configuration. Take a deep breath. We have just started. A lot more to go.

Writing the StoreKit Code

Now that you have setup the iTunes account, let’s start by writing the actual code to interact with the AppStore and allow users to make purchases. For coding help, nothing beats the Apple’s official StoreKit programming guide. However, the guide has one bug that crashes the program. We will see how to circumvent it properly here.

Step 1: Adding StoreKit.Framework

The first step here is to add the StoreKit.Framework to your project.

Step 2: Parental Controls

It’s important to check whether the iPhone/iPod doesn’t have parental control restrictions. When you try to initiate a purchase when parental controls are on, you might crash your app. The apple docs code seems to have an error here. The function, canMakePayments is a static method of class SKPaymentQueue and not a member function. The working code looks like this.

if ([SKPaymentQueue canMakePayments])
{
... // Display a store to the user.
}
else
{
... // Warn the user that purchases are disabled.
}

Step 3: Retrieving the product information and populating the UI

Now the you have designed a gorgeous looking view, you can show it to the user using, say presentmodalviewcontroller or any similar method. Do note that StoreKit doesn’t provide the UI to be displayed. It’s upto the developer to design the UI. The first step is to query your “In-App Purchases” and show the user, the available list of options. Retrieving the product information from AppStore is a couple of lines of code as illustrated here.

- (void) requestProductData
{
SKProductsRequest *request= [[SKProductsRequest alloc] 
initWithProductIdentifiers: [NSSet setWithObject: kMyFeatureIdentifier]];
request.delegate = self;
[request start];
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:
        (SKProductsResponse *)response
{
NSArray *myProduct = response.products;
// populate UI
[request autorelease];
}

The kMyFeatureIdentifier you pass to initWithProductIdentifiers is the uniquely identifiable product id you created in your AppStore account. Remember the reverse DNS product id? You CANNOT specify a wild card character here. So, if you have multiple products, you have to initialize the request object with a list of product using

SKProductsRequest *request= [[SKProductsRequest alloc] 
initWithProductIdentifiers:
[NSSet setWithObjects: myGreatFeature1, myGreatFeature2, nil]];

You can pass as many features as possible to setWithObjects and be sure to end the last object with nil.
Setting the delegate to self and calling the start method will invoke the “didReceiveResponse” delegate. The delegate will give you an array of products and the request itself. Use the array to populate your store UI and release your request here. (This is the same request you created initially).

Step 4: Adding a Transaction Observer

This is a very important step. You can do this as soon as your app is open or when you start a “In-App” purchase session. Do note that, in-app purchase requests are continued even if the user quits the app in between. So imagine a case, when user buys an item, but before the transaction is processed, he gets a phone call that interrupts everything. Though the actual transaction is not interfered (as it happens on Apple servers), your application will never know what happened. To ensure that you get all transaction notifications, (completed/ pending/restored), you have to register a class that receives the callbacks from AppStore. This class should implement and it’s delegate methods.
This is the code for registering the your store observer.

MKStoreObserver *observer = [[MKStoreObserver alloc] init];
[[SKPaymentQueue defaultQueue] addTransactionObserver:observer];

(If you can’t follow anything here, just head below for the real source code, but it’s advised that you read through this, understand and then use the code in your app).

Step 4: Implementing the callback

Within your MKStoreObserver class, you have to implement the callback function paymentQueue:updatedTransactions:

This function will receive updates on the transactions as and when it’s made. Because your transactions take place even when the app is closed, you should be ready to receive these notifications as soon as you open the app. So the best place is to initialize it in applicationDidFinishLaunching or equivalent method.

Now in the updatedTransactions functions, handle the three types of transactions, purchased, failed and restored.

- (void)paymentQueue:updatedTransactions(SKPaymentQueue *)queue 
updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased:
// take action to purchase the feature
[self provideContent: transaction.payment.productIdentifier];
break;
case SKPaymentTransactionStateFailed:
if (transaction.error.code != SKErrorPaymentCancelled)
{
// Optionally, display an error here.
}
// take action to display some error message
break;
case SKPaymentTransactionStateRestored:
// take action to restore the app as if it was purchased
[self provideContent: transaction.originalTransaction.payment.productIdentifier];
default:
break;
}
// Remove the transaction from the payment queue.
[[SKPaymentQueue defaultQueue] finishTransaction: transaction];
}
}

Purchase and failed are seemingly straightforward. You will receive a restored transaction message when your app was quit before the transaction was completed. You should always do the “same” thing when your purchase is new or restored. If you want to charge your users for every download, then probably, set the in-app purchase to be a “consumable” item. But be sure to use that only for purchases that are really “consumable”. Like a live radio show or a podcast and not for unlocking additional levels. Users expect that a level they have unlocked will stay forever.

Three things to note here
1) You should remove the transaction from the payment queue after the transaction is complete. Otherwise, the transaction will be re-attempted, which is not what the users expect (and your app will most likely get rejected).

2) You should provide the content (or unlock the feature) before completing the transaction. When you receive the message SKPaymentTransactionStatePurchased, it means that the users’ credit card has been charged. It’s high time that you provide the feature.

3) You should not display an error when the transaction fails because the user rejected it. Display any error message only when,

if (transaction.error.code != SKErrorPaymentCancelled)</code>

Apple recommends you not to display an error, because that is not an error. The user has purposely cancelled the transaction (probably because the price was too high?). You can audit it and go on. But for heaven sake, don’t display an error like, “Unable to process transaction because user cancelled operation!”. It will be soooo Windozy…

Step 5: The actual purchase

Now that your architecture is ready, you can go ahead and initiate the purchase by calling the function below when the user clicks your “Buy” button on the UI.

SKPayment *payment = [SKPayment paymentWithProductIdentifier:myGreatFeature1];
[[SKPaymentQueue defaultQueue] addPayment:payment];

Optionally, you can add something like payment quantity for “consumable” items.

payment.quantity = n; // number of "items" that user wishes to purchase.

Finally, after providing the feature, you should “remember” that the user has purchased the app. Apple’s recommended way is to use NSUserDefaults, the same way you store your settings.

Testing your app

Alright. Now let’s test the app. Note that, the app cannot be testing from iPhone simulator. The StoreKit communicates with AppStore.app to complete transactions which is not present in the Simulator. So connect your iPhone and run the app.
Remember step 3 you did in iTunes connect? You created some test accounts? Well, they are used for testing your app. Before using that, sign out of AppStore by opening Settings.app -> Store -> Sign Out.
Start the app. (You should NOT sign into the AppStore with the test user account. It will anyway ask you to provide your credit card which we are not interested in)

Open the Store UI and initiate the purchase. You will get a prompt like, “Do you want to buy 1 “ABC feature” for 2.99$? Tap Buy. You will be prompted to login. Provide your test account login. The AppStore provides a secure connection to the iTunes account and notifies you whether the purchase was successful through the callback paymentQueue:updatedTransactions. When you use the test account, the scenarios will be exactly same expect that no one will be charged. These test accounts run inside a sandbox.

In case your app got quit by a phone call, the transactions continue and you will get a restoreTransactions the next time your app is opened. This is why you should start listening to transactions as soon as you open the app (not just when the user opens the store UI)


That’s it. I wouldn’t say its easy. But for developers who have some intermediate knowledge in iPhone development and Objective C, it shouldn’t be a big deal. Remember that when your app is free, you get 10 times more downloads (as reported by admob statistics). When people actually use your app, there is a high chance that they buy your in-app features. That’s upselling becomes easy. Given that this is a very strong business model for the already saturated AppStore, you should start incorporating this model into your code soon.

Source code

The source code, MKStoreKit, contains four files. MKStoreManager.h/m and MKStoreObserver.h/m. The StoreManager is a singleton class that takes care of *everything* Include StoreKit framework into your product and drag these four files into the project. You then have to initialize it by calling [MKStoreManager sharedStorageManager] in your applicationDidFinishLaunching. From then on, it does the magic. The MKStoreKit automatically activates/deactivates features based on your userDefaults. When a feature is purchased, it automatically records it into NSUserDefaults. For checking whether the user has purchased the feature, you can call a function like,

if([MKStoreManager featureAPurchased])
{
//unlock it
}

To purchase a feature, just call

[[MKStoreManager sharedManager] buyFeatureA];

It’s that simple with my storekit. The source code will be uploaded when this post is read by at least 1000 people. Please spread the word.

As always, all my source code can be used without royalty into your app. Just make sure that you don’t remove the copyright notice from the source code if you make your app open source. You don’t have to attribute me in your app, although I would be glad if you do so :)

Downloads:
Version 2:MKStoreKit V2.0
Version 1:MKStoreKit.zip

Troubleshooting

Despite all this, In-App Purchases remain a biggest and the most PITA situation for any iPhone developer. If you can’t get it work, check whether your “didReceiveResponse” delegate returns the product id you passed as invalid. You can confirm this by adding a NSLog statement inside the delegate. Double check if your invalid product id returned here matches the product id you created in iTunes connect. If they are same, check if your product id on iTunes connect says “Cleared for Sale”. For this, you have to provide a screenshot and “Developer Approve” it.

Another case is, if this is your first app, then chances are that, your “Paid Applications Contract” isn’t yet in effect. If this is the case, you have to wait till Apple approves your bank details.

If these two doesn’t work, you might have to wait for 12-24 hrs, till Apple propagates your iTunes connect information to all it’s servers. See the last line in this documentation for more details.

If you have issues adding this to your application, you can hire me to do it for you. I charge a nominal fee of 30$ per hour and this work shouldn’t take more than a couple of hours.

Update 1: (22-Oct-2009): Code changes to fix a crash that occured in most cases.
Update 2: (7-Dec-2009): Added link to MKStoreKit v2.0.

Mugunth

If you enjoyed this post, make sure you subscribe to my RSS feed!

Related posts:

  1. iPhone Tutorial – Enabling reviewers to use your In-App purchases for free In-App purchases is a great way for developers to upsell...
  2. iPhone Tutorial – UISearchDisplayController with NSPredicate Though UISearchDisplayController is seemingly easy (and yes it’s easy), apart...
  3. iPhone Tutorial: Follow Cost API and a open source wrapper What is Follow Cost? Follow Cost is a interesting and...
  4. iPhone Tutorial: Elegant way to send formatted In-App email By now, most of you know how to send emails...
  5. iPhone Tutorial – In-App Email codeproject Sending an email from your iPhone application is...






This website uses IntenseDebate comments, but they are not currently loaded because either your browser doesn't support JavaScript, or they didn't load fast enough.

149 Responses to “iPhone Tutorial – In-App Purchases”

  1. Hardik says:

    Hello Sir
    Instead of using the name of App id can we use the produt Id.for example I created an application and I got the app id that is 34757415.So can i use product id.

  2. Joao says:

    Hi,

    There's this delegate method to know when a transaction has finished with a purchase

    - (void)productPurchased:(NSString *)productId;

    How can i implement to know when the transaction has failed or user has cancelled it?

  3. idev says:

    Hi. I've worked with in-app purchases before without any issues. I am using the same method again for another project. But for some reason the products are returned as invalid. I tried using the fully qualified id as well as just the id (without the com.company part) but get the same response every time.

    Thought I could get some responses here. Would appreciate some help. Thanks.

  4. mugunthkumar says:

    Check your iTunes connect configuration.

    Sent from my iPhone

  5. idev says:

    Appreciate the response. I did check the configuration twice.
    The App ID for the application is the same that I am using in my info.plist and mobileprovision file while testing.
    The app id does not have a wildcard character.
    The in-app purchase products are given numeric IDs and I tried using just the numeric id as well as the fully qualified name to get the purchase info. But in both cases I get invalid products.

    I'm at a loss on what else could be going wrong.

  6. mugunthkumar says:

    Are you specifying the \”fully qualified product name in iTunes connect\”? Just numeric ids in iTunes connect will give this trouble…

  7. idev says:

    I tried both, a fully qualified name and just numeric ids. They both give out errors.

  8. Michael says:

    Hi

    What state does your app have to be in. My app was rejected so l had to add in app purchase. Do l have to resubmit the app?

  9. Andy says:

    Hi,

    Thank you so much for posting this tutorial. Great stuff!
    I am developing an app for a charity, and want users to enter the amount they want to donate through in-app donate button. So there is no product id, is there a way to give the transaction a unique id and then assign to it the amount the user enters? Or any better suggestions to get this done? P.S. I assume every donation would be a consumable purchase.

    • mugunthkumar says:

      u still need a product id. And yeah, as you assumed, every donation would be a consumable purchase. You however cannot allow the user to enter a amount he is willing to donate. The workaround is to create multiple product ids, like
      Donate $1
      Donate $2
      Donate $5 etc., to whatever you feel fit…

  10. Gert-Jan says:

    Hi there,

    Thanks for sharing knowledge :)

    Hope someone is able to help me out on the following scenario;

    In the app we're developing there is a possibility (let's say 1% chance) we cannot deliver a certain (paid) service. The problem is that we cannot check (at least not for free) upfront if the service will be available.

    A solution would be to:
    1) 'charge' the creditcard, assuming the service will be available
    2a) if delivery cannot be made (unavailable service); rollback the 'charge' of the creditcard (undo it) and inform user the charge was cancelled and apologize
    2b) else deliver service, and 'commit' the charge

    Is there a way to do a 'rollback' of the transaction after the charge is made? (or are there any workaround you can think of?).

  11. @marcopapa says:

    BOTH readme.txt files fail to mention that besides the listed calls:

    [MKStoreManager sharedManager];

    [[MKStoreManager sharedManager] buyFeatureA];

    if ([MKStoreManager featureAPurchased] { …}

    one also has to include the code from step "Step 4: Adding a Transaction Observer":

    MKStoreObserver *observer = [[MKStoreObserver alloc] init];
    [[SKPaymentQueue defaultQueue] addTransactionObserver:observer];

  12. @marcopapa says:

    I think that if your delegate responds to the selector "productPurchased", you'll get called as:

    [_delegate producPurchased:productIIdentifier];

    and can then the notified as a purchase "event", not by polling featureAPurchased.

  13. Ivan says:

    Hi! Thanks for the tutorial!

    I have implemented it in my new game and it's running ok with iphone's but with ipod's don't work.
    In the ipod i can't read the price of my levels. Any idea?

  14. @marcopapa says:

    MK, there is a problem with the code. You have featureAPurchased as BOTH the name of a method and the name of a BOOL variable. That seems to screw up the generated code. I changed the name of the BOOL variable to featureAPurchasedFlag (and did the same for featureBPurchasedFlag) and now the code works.

  15. @marcopapa says:

    MK, there is a problem with the code. You have featureAPurchased as BOTH the name of a method and the name of a BOOL variable. That seems to screw up the generated code. I changed the name of the BOOL variable to featureAPurchasedFlag (and did the same for featureBPurchasedFlag) and now the code works.

  16. Wayne Lo says:

    Hi Mugunth,

    Great thanks for the tutorial and example code. Apple should have hire you to help them clean up this part. I have a question regarding releasing objects in MKStoreManager. Since you do not release the purchasableObjects in either relase or autorelease function, how do you release this object?

    Thanks,

    Wayne

  17. @marcopapa says:

    Another suggestion: flush the new UserDefaultSettings:
    ….
    [userDefaults setBool:upgradeAPurchasedFlag forKey:upgradeAId];
    // Writes the modifications to the persistent domains to disk
    [[NSUserDefaults standardUserDefaults] synchronize];

  18. Amit says:

    Hi Mugunth,

    Great thanks for the tutorial and example code. First time I am working on this stuff,I have read your documentation but I am having one question that is it necessary we have to register each product or element ,If I am having same kind of product like music and having the same value(Cost) for all and I have to change these files dynamically..Is it necessary that I have to register each music file for In App Purchase. Is there any way to handle this dynamically.

    Please send me the process how can I handle this…

    Thanks in Advance…. please reply ASAP…

    Amit

  19. John Kern says:

    Generic question: How secure is to store user purchases in the 'NSUserDefaults'? I guess that anyone with access to the iPhone directory can modify this settings and can get a new feature unlocked. Should we use special encrypted files to store purchases?

    • Gaurav Agarwal says:

      Yes as per my view. I am encrypting the purchase information using AES and storing in Document directory. When I require to read/modify it, I decrypt that in memory and read/modify again, again encrypt and save onto disk.

  20. Ashish says:

    HI,
    Can u please tell me the meaning of line in step 2 i.e "If your app is not yet live, you can create a dummy, placeholder application, fill in the metadata (which you can anyway change it later) and check “upload binary later” " as i am unable to understand how to create a placeholder application.

    Thanks
    Ashish

  21. abc says:

    how to create product references ?
    i mean i have a product "A", still not uploadded in app store,
    i have 3 features in it, say x.y.z
    now the prize for each one is different how inividually buy this features..?

    • Gaurav Agarwal says:

      Lets say your identifier for "A" is "com.yourcompanyname.A"

      So for x, y and z you need to create three more products in itunes account for in app purchase with following identifiers:
      com.yourcompanyname.A.x, com.yourcompanyname.A.y and com.yourcompanyname.A.z. And set the pricing for each sub product what you want.

  22. abc says:

    Ya finally got working..
    but what are the possible messages that can come from appstore
    can i have list of such messages with proper documentation from apple ?
    Nice work..!!!

  23. JohnCST says:

    Thanks for the very helpful tutorial. The code is easy to implement and use. I'm having an issue I haven't seen mentioned. When I try to buy a product in the sandbox, I get the normal buy verification alert with the correct product ID and price. When I press BUY, I get another alert saying that "Your account info has changed. Tap Continue to sign in, then review and confirm the changes. [Environment: Sandbox]" If you press Cancel, control is returned to my code and I can press another selection or exit. If I press Continue, my code exits (return code 0, it is not a "crash"), but the store loops asking for my password, then presenting the "account info has changed" alert. Any ideas?

    • mugunthkumar says:

      you should create a test user account, signout of the appstore on your test device, use the test user account to test the in-app purchases. This is explained in Step 4 of \”Preparing the iTunes Connect\” section.

      Thanks,
      MK

      • JohnCST says:

        Thanks for your reply. I did read your explanation, and I did create an in-app purchase test user. I insured I was not logged into any Apple Web site. On the device, in Setup I configured the Store info to use the test user. I entered a valid credit card, address, etc., and was able to log into the iTunes store. Should I not enter credit card info and leave Apple's address? Thanks in advance for your suggestions.

  24. La'fear says:

    Just wonder if any developer is at the similar stage. I'm planning to make a transition an existing paid app to free version with "In App Purchase". For example, the original one is version 1.0 and it's a paid app. After updating to version 2.0, it will be free + "In App Purchase". What I concerned is that will the original version 1.0 buyers be charged again because this kind of transition?

    • mugunthkumar says:

      It's not easy to do this. But you can use the work around i explained in this blog post.

      http://blog.mugunthkumar.com/coding/iphone-tutori...

      Release a minor update to your "paid" app and collect all Device IDs in your server. Use MKStoreKit v2.0 and setup your server with the sql scripts given. Set the ownServer property to your server location. MKStorekit will automatically ping your server to check your device id tables to check if the user has already purchased the paid app and enables the in-app for free.

      Or come back to my blog by this weekend. I'm actually writing a tutorial on this right now :)

Leave a Reply


8 visitors online now
8 guests, 0 members
Max visitors today: 19 at 05:00 pm SGT
This month: 58 at 03-01-2010 02:57 am SGT
This year: 59 at 02-12-2010 06:23 am SGT
All time: 59 at 12-19-2009 09:48 pm SGT