iPhone Tutorial – In-App Purchases

Update: MKStoreKit 4.0, an updated version of the one presented here is available. Please check it out and use 4.0 instead of this.Update: A Chinese translation is available here.
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.

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.

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

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.

(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.

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)

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.

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:Update: MKStoreKit 4.0, an updated version of the one presented here is available. Please check it out and use 4.0 instead of this.

Version 2:MKStoreKit V2.0Version 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 $350 – $700(depending on the complexity) for the whole integration and it should take approximately half a day to one day. You can also approach me for a customized code that suits your business model as well.

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.Update 3: (4th Dec 2010) MKStoreKit 3.0, an updated version of the one presented here is available. Please check it out and use 3.0 instead of this.Update 4: (8th July 2011) MKStoreKit 4.0, an updated version of the one presented here is available. Please check it out and use 4.0 instead of this.

Support me

Hourly rates of a iPhone developer is skyrocket high. I believe this code would have saved your coding hours by at least a day. You can consider supporting further development by funding me through PayPal. My PayPal email is mugunth.kumar@gmail.com
–
Mugunth

Hi Mugunth Kumar, i’m newbie
when i change the price of items in itunes connect, how can i update it to my app??Thanks a lot!!

iOS Developer

Hi,

Thanks for the entire tutorial. It’s very useful.

I have set all the things like price, description, screenshots for In-App.
I have implement In-App purchase successfully and my app is also approved by Apple.

But I have one issue.
My app is listed on App Store. But in that, “Top In-App Purchase” section is not displaying. So, user doesn’t know that, my app has In-App purchase and user can get full version through In-App.

Is there anything miss?

Somewhere I have read that, when one user will purchase In-App then after this section will be displayed. Is it like so?

Just to say thank you so much for posting this tutorial, I’m sure it would have taken me a long time to get this going had I not seen this.

guest

Hi, Thank you for tutorial
Let’s say I want to make in app purchase for a magazine. Will your MKStoreKit work with newsstand on iOS5?

http://pulse.yahoo.com/_ZQL7U6T2SLDL5O5QHDKXGF323U Brad

1. I wanted to test this with multiple accounts, so I deleted the app and signed out of my test user 1. I reinstalled with app but:

if([MKStoreManager isFeaturePurchased:@"com.myapp.feature"])

keeps returning true even though I would assume it shouldn’t be? I want to test the in-app process in a few different ways but can’t seem to test it after I already purchased it.

2. How do I differentiate between failed requests, and cancelled requests?

Thanks in advance for your help. After figuring out these few things, my in-app purchase headache will be complete and I would be more than happy to make a donation. Thanks.

http://2miniman.com/ MiniMan

I keep getting the same problem. It’s always true when I do if([MKStoreManager isFeaturePurchased:@"com.myapp.feature"]).

Does anyone know a fix for this, please?

Hokuto2035

i have the same problem too. when i try to delete the item use “removeAllKeychainData” it returns Error says “SFHFKeychainUtilErrorDomain Code = -25300″.
Why it can not be clear?

James

We have a new app which we are going to submit to apple and it will have in app purchase.
Third party companies want to give our app away for free to their subscribers and pay us a lump sum to do so. Will apple allow us to do that and will they want any part of the lump sum we get ?
If they did allow it without requiring payment we would want to add “do you have a promo code” prior to the in app purchase and give the promo codes to the third parties. Their subscribers would then enter the promo code to get the app for free.
If it is possible – can you write the software for us ?

Inalbansal

After searching through this forum and online for hours, I
can’t find an answer to the problem I’m having. Here’s the basic
scenario:

Trying to make an in-app purchase in my app in the sandbox
environment always fails. Getting the list of available products
through SKProductsRequest works fine .. I get an array of valid
SKProduct objects back. But after I queue an SKPayment in the
SKPaymentQueue, the transactionState of the SKPaymentTransaction object
that the observe gets is SKPaymentTransactionStateFailed. HOWEVER, when
I try to see WHY it failed, I find that the error property of the
SKPaymentTransaction object is nil. There is no NSError object there to
query!!

Also, in the app, no pop up ever appears to ask for the account
credentials to verify the purchase! And yes, I did first log out of my
real Apple ID in the Settings app, and I did not try to sign back in there with my test account ID. That way, there should
be a popup when I try to make a purchase asking me for my Apple ID
info, where I would enter the test account info. But that popup never
appears. The transaction just silently fails.

If I query the payment property of the failed SKPaymentTransaction
object, I find the correct SKPayment object with the correct
productIdentifier, so the failed transaction is at least asking for the
right product. So why is it failing?

Has anybody else seen this?

I’ve gone through all of the proper setup steps listed in the
documentation and elsewhere in this forum … I created a non-wildcare
app ID, activated in-app payments for that ID, created a new, dummy app
in iTunes Connect (checking “upload binary later”), created some in-app
purchase products, made them available for sale, created a new developer
provisioning profile for the new app ID, downloaded that profile and
installed it in Xcode and on my test phone, changed the bundle
identifier in the info.plist of the app, changed the code signing to use
the new provisioning profile, set up a test user in iTunes Connect with
a novel email address, etc etc etc etc etc. Like I say, I do
get an array of valid SKProduct objects from the app store servers, so
things must be set up somewhat correctly. But even after redoing
everything from scratch a few times and tweaking everything I can think
of, I can’t get a sandbox purchase to go through .. or even to get the
confirm purchase popup!

Any ideas?

Ryan Davis

I have this exact same issue. If you ever figured it out, let me know. For me, I even get the popup when clicked in the simulator, just not on an actual device, where it silently fails like you describe. I set up everything like you stated as well.

Hi,
1. Great blog!
2. I was just wondering how I can detect when the ‘Confirm Your In App Purchase’ alert is displayed on screen?
- I tried setting up the (void)didPresentAlertView:(UIAlertView *)alertView; from UIAlertViewDelegate but that doesn’t catch the In App Purchase popup.

Any ideas?
Thanks!

cujo30227

Hi. After upgrading to XCode 4.3, the MKStoreKit is not working properly anymore… With Xcode 4.2m it worked perfectly, with 4.3 I now get the message: “Problem in iTunes connect configuration for product: xxxx”. Product ID and everything looks fine. Any clues ??

Pete

thanks for the code, one comment though. it uses blocks extensively. so i cant use it on my ios 3.1 project.

Gypsa

I am following the way for in app purchase you provided.My problem is completeTransaction method is getting called multiple times.Can you suggest me is this happening or what should I do to prevent this.My requirement is downloading many tracks on complete transactions but beacuase complete transaction is calling many times , my tracks are getting download many times.

Kuzma9

I am setting up MKStoreKit 4.0. It gives me a compiler error for this line of code:

“#error MKNetworkKit is ARC only. Either turn on ARC for the project or use -fobjc-arc flag”

When I with to run the app in ARC I get about 100 compiler errors. Could you explain more on how to set up -fobjc-arc flag? Thanks.

Ryan Davis

Go to the Build Phases section of your app by clicking the big blue main app icon in Xcode and then selecting your app under Targets. Select the Build Phases tab and go down to Compile Sources. Double-click to the right of the files used in MKStoreKit, which should be at the bottom of the chronologically-arranged list. When you double-click a little popup will appear that you can paste things into. Paste -fobjc-arc into there. That will instruct the compiler to only compile those files using ARC (and the rest of your project can be non-ARC).

Jim Wolff

Apple rejected my app that used auto-renewable subscriptions with MKStoreKit and told me to make it a non-renewable subscription. Anyone know how to use a non-renewable subscription with MKStoreKit? Non-renewable expiration must be managed manually according to apple. When I put it in the subscriptions part of the plist it doesn’t seem to be working. Couldn’t find anyone on web using MKStoreKit for non-renewable subscription. Anyone know steps for doing this right with MKStoreKit?

MugunthKumar

Non renewable subscription is like a non-consumable. You should however keep track of subscriptions on your server

Chano

Here’s how it goes, I already have 3 apps, Now what I am trying to do is to make an app that will serve like a store where they can browse my apps and buy/download them inside the app (no iTunes link), then once downloaded, they can open/use it inside the app (no separate icon). Something like the magazine apps on Newsstand that let you download an app, store it inside the app and use it inside the app.
Does in-app purchase has anything to do with it?

K Subodh1

I have to add subscription feature for my iPad application. I just want to add in app purchase functionality. My app subscription concept is like, when user download application from itune and install on iPad. Once user try to run the application. App force user to subscribe app for monthly or yearly etc. after subscribing app for monthly or yearly then only user can use the app.
But i am not sure how to use in app purchase in this case.
I have seen only digital product can be used in app purchase. but i want to use my app itself for purchase.
any help?

K Subodh1

any idea

http://www.facebook.com/raniwala25 Arpit Raniwala

Hey Subodh.
Although i am fairly new to iOS development but in my understanding what you are trying to implement is, when the APP launches, you check whether a user has purchased the subscription for your app or not…. if yes then you let him continue using the app else you prompt him to subscribe…

In this context you can create a subscription based in-App purchase for your app and in the AppDelegate itself in ApplicationDidFinishLaunchingWithOptions method in your AppDelegate.m, you can check whether the user has subscribed to the app or not and you can continue accordingly. Please comment if you feel this may have helped you.

This code is working good in ios 6 but not working in ios 7. when do purchase it shows in app purchased failed..

I am able to receive the productsRequest:didReceiveResponse method, and receive the array of products.

My problem arises when I add a SKPayment to the SKPaymentQueue. After
I add the product to the queue, in the paymentQueue:updatedTransactions
method the transactions always have the state
SKPaymentTransactionStateFailed.

I NSLog the “transaction.error” and this is what it returns: Error
Domain=SKErrorDomain Code=0 “Cannot connect to iTunes Store”
UserInfo=0x17d51970 {NSLocalizedDescription=Cannot connect to iTunes
Store}