I recently had the opportunity to attend the CocoaConf in Washington, D.C. and sat in on an overview of In-App Purchases in iOS 6 and thought to myself... "Hey, that would be great for my app." So I figured I would give it a go. And without further ado, here's my implementation of In-App Purchases.

The first thing to understand is that there are five different kinds of In-App Purchases.

  • ​Consumable: The same item for one user can be purchased many times.
  • Non-Consumable: An item is only bought once by the user and they will retain ownership.
  • Auto-Renewable Subscription: Consumers are charged at regular intervals.
  • Non-Renewing Subscription: Consumers are charged once but can choose to purchase it again later.
  • Free Subscription: Useful for keeping track of customers.

As a starting point, it made the most sense for me to create a simple non-consumable In-App Purchase to unlock advanced features in my app. I chose this because it was the easiest to start with, but there are many more advanced features you can use, such as storing unlockable content with Apple or hosting it on your own server. I don't cover this here, but this blog post should give you a good starting point to more easily understand the Apple documentation on In-App Purchases.

Step 1 - Adding a Product to iTunesConnect

The first thing you must do is add a new product to your app in iTunesConnect. If you haven't added a new app in iTunesConnect, you must do that first. Understandably, this may be confusing because of the large icon and screenshot requirements to create an app, but they don't have to be real. You can insert dummy data temporarily and replace it with real data later. Once you have your app set up in iTunesConnect, open it and you will see a button that says "Manage In-App Purchases".

Manage Purchases

Here you will click "Create New" to create your In-App Purchase. You will then be prompted to select one of the In-App Purchase types I talked about earlier. I selected "Non-Consumable" because I wanted ownership of the paid features to be retained by the buyer. Finally fill in all the required info. The required fields are mostly self-explanitory and well documented by question mark links found beside each of the fields.

Step 2 - Adding a Test User in iTunesConnect

Next you will need to add a user that can test your In-App Purchases. This is simple and can be done from iTunesConnect by clicking "Manage Users" from the main dashboard and then "Test User" in iTunesConnect.

Once an In-App Purchase has been created, added to the app, and you have created a test user, you are ready to start coding. Keep in mind; it takes some time to propagate the product you created. For me it took only a minute or two, but I have heard it can sometimes take up to one or two days. So maybe now is a good time to get a coffee.

Step 3 - Code

It's important to understand the purchase process of an In-App Purchase. Let me explain. This is the flow of a simple In-App Purchase:

  1. Load the product identifiers for the purchase you created in iTunesConnect
  2. Request the product info
  3. Present a store or mechanism to buy the product in your app
  4. Issue a payment request
  5. Process a transaction request
  6. Unlock your app content or provide a product to the customer
  7. Finish the transaction

For my app, I decided to create a singleton class to be used for all my In-App Purchases. I created this model class to be run from my app and do all the heavy lifting.

I began by adding a constant for the product ID because I knew it would be used multiple times.

<span class="co1">#define kProductId @"com.captech.someapp.someproduct"​</span>

I also broadcast that my singleton conforms to the StoreKit delegate and transaction observer required for the In-App Purchases in my model's header.

,>

I then moved on and created a purchase method that could be called to initiate the purchase. I made this method with the intention of it kicking everything off by loading the product identifiers and then requesting the product info.

<span class="sy0">-</span> <span class="br0">(</span><span class="kw4">void</span><span class="br0">)</span>purchase <span class="br0">{</span>
 <span class="co2">// Request product identifiers</span>
 <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/"><span class="kw5">NSSet</span></a> <span class="sy0">*</span>productIdentifiers <span class="sy0">=</span> <span class="br0">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSSet_Class/"><span class="kw5">NSSet</span></a> setWithObject<span class="sy0">:</span>kProductId<span class="br0">]</span>;
 SKProductsRequest <span class="sy0">*</span>productsRequest <span class="sy0">=</span> <span class="br0">[</span><span class="br0">[</span>SKProductsRequest alloc<span class="br0">]</span> initWithProductIdentifiers<span class="sy0">:</span>productIdentifiers<span class="br0">]</span>;
 productsRequest.delegate <span class="sy0">=</span> self;
 <span class="br0">[</span>productsRequest start<span class="br0">]</span>;
 
 <span class="co2">// Create payment request</span>
 <span class="kw1">if</span><span class="br0">(</span><span class="br0">[</span>SKPaymentQueue canMakePayments<span class="br0">]</span><span class="br0">)</span> <span class="br0">{</span> <span class="co2">// We're accepting payments</span>
 SKMutablePayment <span class="sy0">*</span>payment <span class="sy0">=</span> <span class="br0">[</span><span class="br0">[</span>SKMutablePayment alloc<span class="br0">]</span> init<span class="br0">]</span>;
 payment.productIdentifier <span class="sy0">=</span> kProductId;
 payment.quantity <span class="sy0">=</span> <span class="nu0">1</span>;
 
 <span class="co2">// Add the payment to our payment queue to be processed</span>
 <span class="br0">[</span><span class="br0">[</span>SKPaymentQueue defaultQueue<span class="br0">]</span> addPayment<span class="sy0">:</span>payment<span class="br0">]</span>;
 <span class="br0">}</span>
<span class="br0">}</span>

If you noticed, I also created my payment request. Now your thinking, "Well, how did he skip steps 2 and 3? (Requesting product info and presenting the store.)" For my app, my store was a button that says "Purchase". So I didn't need any product info data to present a store.

A delegate method will be called when the product identifiers are received. When they are received the method called will be productsRequest:didRecieveResponse:.

<span class="sy0">-</span> <span class="br0">(</span><span class="kw4">void</span><span class="br0">)</span>productsRequest<span class="sy0">:</span><span class="br0">(</span>SKProductsRequest <span class="sy0">*</span><span class="br0">)</span>request didReceiveResponse<span class="sy0">:</span><span class="br0">(</span>SKProductsResponse <span class="sy0">*</span><span class="br0">)</span>response <span class="br0">{</span>
 <span class="co2">// Response for product identifiers succeeded</span>
 <span class="br0">[</span><span class="br0">[</span>SKPaymentQueue defaultQueue<span class="br0">]</span> addTransactionObserver<span class="sy0">:</span>self<span class="br0">]</span>;
<span class="br0">}</span>​

Here I set up a transaction observer to handle the payment of the purchase, but if you have a store, this would be a great place to get the product data and display it to the user. I didn't do it for this example, but Apple recommends adding the observer at app initialization so that failed transactions will be restored.

If you want to get the product data, the code would look like this...

 
<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span class="kw5">NSArray</span></a> <span class="sy0">*</span>products <span class="sy0">=</span> response.products;
<span class="kw1">if</span><span class="br0">(</span>products.count > <span class="nu0">0</span><span class="br0">)</span> <span class="br0">{</span>
 SKProduct <span class="sy0">*</span>product <span class="sy0">=</span> <span class="br0">[</span>products objectAtIndex<span class="sy0">:</span><span class="nu0">0</span><span class="br0">]</span>;
 
 <span class="kw1">if</span><span class="br0">(</span>product<span class="br0">)</span> <span class="br0">{</span>
 NSLog<span class="br0">(</span><span class="co3">@</span><span class="st0">"Title: %@"</span>, product.localizedTitle<span class="br0">)</span>;
 NSLog<span class="br0">(</span><span class="co3">@</span><span class="st0">"Description: %@"</span>, product.localizedDescription<span class="br0">)</span>;
 NSLog<span class="br0">(</span><span class="co3">@</span><span class="st0">"Price: %@"</span>, product.price<span class="br0">)</span>;
 NSLog<span class="br0">(</span><span class="co3">@</span><span class="st0">"Id: %@"</span>, product.productIdentifier<span class="br0">)</span>;
 <span class="br0">}</span>
<span class="br0">}</span>
 
<span class="kw1">for</span> <span class="br0">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span class="kw5">NSString</span></a> <span class="sy0">*</span>invalidProductId <span class="kw1">in</span> response.invalidProductIdentifiers<span class="br0">)</span> <span class="br0">{</span>
 NSLog<span class="br0">(</span><span class="co3">@</span><span class="st0">"Invalid Product: %@"</span> , invalidProductId<span class="br0">)</span>;
<span class="br0">}</span>​

After I set up the observer to handle the transaction, all I needed to do was process the transaction, unlock the paid app content, and finish the transaction.

The transaction observer will respond to paymentQueue:updatedTransactions:.

<span class="sy0">-</span> <span class="br0">(</span><span class="kw4">void</span><span class="br0">)</span>paymentQueue<span class="sy0">:</span><span class="br0">(</span>SKPaymentQueue <span class="sy0">*</span><span class="br0">)</span>queue updatedTransactions<span class="sy0">:</span><span class="br0">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span class="kw5">NSArray</span></a> <span class="sy0">*</span><span class="br0">)</span>transactions <span class="br0">{</span>
 <span class="kw1">for</span> <span class="br0">(</span>SKPaymentTransaction <span class="sy0">*</span>transaction <span class="kw1">in</span> transactions<span class="br0">)</span> <span class="br0">{</span>
 <span class="kw1">switch</span> <span class="br0">(</span>transaction.transactionState<span class="br0">)</span> <span class="br0">{</span>
 <span class="kw1">case</span> SKPaymentTransactionStatePurchased<span class="sy0">:</span>
 <span class="co2">//Purchase succeeded</span>
 <span class="br0">[</span>self unlockAppContent<span class="sy0">:</span>transaction.payment.productIdentifier<span class="br0">]</span>;
 <span class="kw2">break</span>;
 <span class="kw1">case</span> SKPaymentTransactionStateRestored<span class="sy0">:</span>
 <span class="co2">//User already purchased it before</span>
 <span class="br0">[</span>self unlockAppContent<span class="sy0">:</span>transaction.originalTransaction.payment.productIdentifier<span class="br0">]</span>;
 <span class="kw2">break</span>;
 <span class="kw1">case</span> SKPaymentTransactionStateFailed<span class="sy0">:</span>
 <span class="kw1">if</span> <span class="br0">(</span>transaction.error.code <span class="sy0">!=</span> SKErrorPaymentCancelled<span class="br0">)</span> <span class="br0">{</span>
 <span class="co2">// Transaction failed</span>
 <span class="br0">}</span> <span class="kw1">else</span> <span class="br0">{</span>
 <span class="co2">// User cancelled transaction</span>
 <span class="br0">}</span>
 <span class="kw2">break</span>;
 <span class="br0">}</span>
 
 <span class="co2">// Finish the transaction</span>
 <span class="kw1">if</span><span class="br0">(</span>transaction.transactionState <span class="sy0">!=</span> SKPaymentTransactionStatePurchasing<span class="br0">)</span> <span class="br0">{</span>
 <span class="br0">[</span><span class="br0">[</span>SKPaymentQueue defaultQueue<span class="br0">]</span> finishTransaction<span class="sy0">:</span>transaction<span class="br0">]</span>;
 <span class="br0">}</span>
 <span class="br0">}</span>
<span class="br0">}</span>

Since I used a queue I could have potentially processed multiple transactions. This simple app though only used one. The transaction observer will come back with various state changes for the transactions. It could have succeeded, failed, been restored, or is still processing.

For this simple app I just wanted to unlock some app content if it succeeded or was restored, so I called unlockAppContent: which I called from the paymentQueue:updatedTransactions: method above.

<span class="sy0">-</span> <span class="br0">(</span><span class="kw4">void</span><span class="br0">)</span>unlockAppContent<span class="sy0">:</span><span class="br0">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span class="kw5">NSString</span></a> <span class="sy0">*</span><span class="br0">)</span>productId <span class="br0">{</span>
 <span class="kw1">if</span> <span class="br0">(</span><span class="br0">[</span>productId isEqualToString<span class="sy0">:</span>kProductId<span class="br0">]</span><span class="br0">)</span> <span class="br0">{</span>
 <span class="co2">// Unlock app content</span>
 <span class="br0">}</span>
<span class="br0">}</span>

After one last sanity check to make sure the correct In-App Purchase was purchased, the app will unlock the paid app features. Unlocking the app content could potentially do anything you want it to. In my case, I had it change some NSUserDefaults and unlock some advanced features of the app.

The final step in processing a purchase is finishing the transaction. You might have noticed the code earlier in the paymentQueue:updatedTransactions: method. If you look in the observer code above, you will see it runs the method finishTransaction.

There! Wasn't that easy?

Step 4 - Testing and Problems

All testing for your app will be done in the Sandbox which will simulate a store for your fake purchases with your test user. Your app is linked to the product, so the purchases will only work in the real store when your app and product have been approved.

Log out of your current user account on your iPhone (Settings->iTunes & App Stores->Apple ID:...->Sign Out) or reset the simulator settings and test out your new In-App Purchase.

If your having trouble, likely the In-App Purchase product is still propagating through Apple's servers, so you may need to wait a bit. I thought this was the case for me when my purchase would fail on my device, but it ended up not being related to the In-App Purchase propagation. My purchases would work in the simulator but not on my phone. I had to reset all my settings in order to get it working by going to (Settings->General->Reset->Reset All Settings).

Also check to make sure the Sandbox is up and running by going to https://sandbox.itunes.apple.com/verifyReceipt

Other Noteworthy Things

In-App Purchases make not only the app store cleaner but increase our chances of selling apps. The top grossing apps in the app store are typically free and implement In-App Purchases. Instead of having a paid and free version of an application, you can create one version that everyone can download. So instead of a user shrugging off a paid only version they see in the app store, they can download the free version and then decide later to purchase it. For some statistics on In-App Purchases revenue, check out this article on TechCrunch. This small tutorial was just the tip of the iceberg. In-App Purchases can do lots more. For example, you can also create a button to automatically restore completed transactions. You can do this with one line of code.

<span class="br0">[</span><span class="br0">[</span>SKPaymentQueue defaultQueue<span class="br0">]</span> restoreCompletedTransactions<span class="br0">]</span>;​

The true power of In-App Purchases appears in the Server Based Integration and Receipt Processing. You can now store content packages with Apple that can be distributed with In-App Purchases thus lowering app size, making your app more secure, and removing any need for your own server. It's a larger feature to implement, but the idea is simple. You will enable hosting content with Apple in your iTunesConnect Purchase, create content packages in Xcode that wil be uploaded to Apple, and process the download when a purchase is completed using SKPaymentQueue:startDownloads: and it's delegate response method, paymentQueue:updatedDownloads:.

One more thing to remember, you must always be aware of security and be very careful in processing receipts. Many hacker attempts have succeeded from apps validating receipts incorrectly. Just remember to only accept receipts you know came from a reliable source such as Apple or your own server.

I hope you've enjoyed my brief overview of Apple's In-App Purchases. I want to thank Ray Wenderlich for his presentation at the DC CocoaConf and providing the inspiration for this blog post.