•   In App Purchase Tutorial  

    September 21st, 2010

    In App Purchase is a great tool for sales setup if you offer additional functionality or features to your iPhone application. But it’s not that easy to get the job done…

    Here are some key tips for working with In App Purchase that will help you to add your products to the AppSore.

    – In App Purchase Process

    – Purchased Products Types in the In App Purchase

    – Model of Server – Application Interaction in the In App Purchase

    – Pitfalls in Choosing Purchase Types in the In App Purchase

    – In App Purchase Sample Code and Implementation

    – Creating the Purchase Product and Test User in itunesconnect

    In App Purchase Process

    The connection with the AppStore is carried out through StoreKit.framework that is delivered  with SDK starting from the 3.0 version. The core steps of the  process are the following:

    • the developer using itunesconnect.apple.com creates some virtual product with a name, description and price
    • the application connects to the AppStore and purchases the virtual product
    • the application makes required changes or/and  does certain actions (unlocks particular levels, makes a sound, enables monthly subscription for access to the information storage, magazine archive, etc. )

    Purchased Product Types in the In App Purchase

    There are three types of the products you can create:

    Consumable
    Non-Consumable
    Subscription

    Consumable. According to the type name the product can be purchased several times. For example, arrows or bullets for a game.
    Non-Consumable. Purchase can be done only once. This type of the purchase is used to unlock new themes, additional levels and so on.
    Subscription. Subscription to any kind of service. This type can be used for a web service app to purchase monthly premium account. Product of this type must be supported by different devices (iPhones/ iPads) and user can make entry using one account.

    Note! StoreKit.framework offers tools to get transactions history, but the history will contain only the Non-consumable products transactions. For other types of products you should create or use other server to store this information.

    Model of Server – Application Interaction in In App Purchase

    A model of the server interaction can be described in the next 8 main steps:

    • The application requests the list of the products from your server
    • The app displays the list of the new products to the user
    • The user either purchases the product or doesn’t buy it
    • The app requests the purchase from the Apple’s server using StoreKit
    • StoreKit sends the answer as a transaction
    • iPhone application sends to its server a so-called receipt
    • Application’s server validates the receipt on the Apple’s server
    • The iPhone app downloads the product from its server

    Pitfalls in Choosing Purchase Types in the In App Purchase

    Apple is rather strict about the purchase type you’re using for the application.

    If the developer has an intention to create monthly subscription for current updates (let’s say to get new jokes from the web), but has no desire to implement the support for the range of devices, then he can’t use this purchase type. The Subscription type forces you to implement the support of many devices though Apple doesn’t provide any tools to facilitate this part of development.

    The Consumable type seems to be a simple decision to make in such a situation: it doesn’t require devices support and it’s easy to use. BUT: when trying to get project approval from Apple you’ll face the  rejection as Purchase doesn’t corresponds application’s logic and confuses the user. Thus, if there is any hint on Subscription logic (or Apple can see it) the developer is forced to implement all the necessary features (Aha!).
    The uncertain regulation provided by Apple is the pitfall for the developers who are trying to estimate the application with In App Purchase, but have never had a deal with it before.

    The best way out is to follow the implementation model provided by Apple. If  it’s said in Apple’s docs “We recommend you  to …” you should read “Implement the application this way only… or else…”

    In App Purchase Sample Code and Implementation

    Here is an example of the In App Purchase implementation structure for a simple case

    In App Purchase interconnection model
    Pic. 1. Architecture of the Update Unit

    Here we have an application that has one product of the subscription type that gives you the opportunity to update twice a month.  The application is free but has no trial period. Instead of the trial period the app has some firmware that let you use the app without purchasing the updates subscription.

    Updater is a class which task is to download and setup updates for the application. Before each update it checks for the updates permission. To perform this the Updater refers to the Local Storage.

    Local Storage is an abstract and optional unit that stores information about subscription starting and expiry dates (preferably codified) on a local basis. Local Storage is made to save some traffic and secure the situation when the billing server is unavailable.

    If the Local Storage has no information the Updater decides the app can be reinstalled and refers to AccountManager. AccountManager returns all the required information or starts the registration or log in process.

    When the date information is obtained the confirmation is carried on. If the subscription isn’t expired the update is performed otherwise the request to InAppPurchaseManager takes place. The update purchase is offered to user. Then using storeKit.framework tools the InAppPurchaseManager carries on the transaction and returns the result to Updater. The Updater sends the receipt to BillingServer to validate it. After successful validation Updater gets a new subscription date, saves it to Local Storage and updates the information.

    We can’t put here all the listings of the LocalStorage and AccountManager as the their code will require dozens of pages. But we give you In App Purchase sample code from the real project where it was called InAppPurchaseController.

    
    //-----------InAppPurchaseController.h--------------
    
    #import <Foundation/Foundation.h>
    
    #import <StoreKit/StoreKit.h>
    
    @class inAppPurchaseController;
    

    This class interconnects with the Updater using protocol that sends messages when the info about the product and payment transaction (or error)  is uploaded (otherwise the error occures).

    @protocol inAppPurchaseControllerDelegate
    
    - (void) purchaseController: (inAppPurchaseController *) controller didLoadInfo: (SKProduct *) products;
    
    - (void) purchaseController: (inAppPurchaseController *) controller didFailLoadProductInfo: (NSError *) error;
    
    - (void) purchaseController: (inAppPurchaseController *) controller didFinishPaymentTransaction: (SKPaymentTransaction *) transaction;
    
    - (void) purchaseController: (inAppPurchaseController *) controller didFailPaymentTransactionWithError: (NSError *) error;
    
    @end
    

    strPurchaseId is the class property that implies the work with the single product at the instant moment. The class can get the Product description and then purchase it.

    @interface inAppPurchaseController : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver>
    {
     NSString   *strPurchaseId_;
      id <inAppPurchaseControllerDelegate>   delegate_;
    }
    
    @property(assign) id <inAppPurchaseControllerDelegate> delegate_;
    @property(copy) 	NSString    *strPurchaseId_;
    
    - (void)    loadProductsInfo;
    - (void)    makePurchase: (SKProduct *) product;
    - (BOOL) isPresentNonFinishedTransaction;
    
    @end
    
    //-------	InAppPurchaseController.m-----------
    
    #import "inAppPurchaseController.h"
    
    @implementation inAppPurchaseController
    
    @synthesize strPurchaseId_;
    @synthesize delegate_;
    

    With the initialization the class subscribes to messages from SKPaymentQueue. There is payments queue where the query is send to.

    - (id) init
    
    {
    
    if(self = [super init])
    
    {
    
    [ [SKPaymentQueue defaultQueue] addTransactionObserver: self];
    
    }
    
    return self;
    
    }
    
    - (void) loadProductsInfo
    
    {
    
    SKProductsRequest *request = [ [SKProductsRequest alloc] initWithProductIdentifiers: [NSSet setWithObject: strPurchaseId_]];
    
    [request setDelegate: self];
    
    [request start];
    
    }
    

    Before the payment transaction is added to the queue it’s necessary to check whether it can be done at the moment. Which is implemented by the call of the canMakePayments method from the class SKPaymentQueue.

    - (void) makePurchase: (SKProduct *) product
    
    {
    
    if(![SKPaymentQueue canMakePayments])
    
    {
    
    [delegate_ purchaseController: self didFailPaymentTransactionWithError: [NSError errorWithDomain: @"payMentDomain" code: 123 userInfo: [NSDictionary dictionaryWithObject: @"can not make paiments" forKey: NSLocalizedDescriptionKey]]];
    
    }
    
    SKPayment *payment = [SKPayment paymentWithProduct: product];
    
    [ [SKPaymentQueue defaultQueue] addPayment: payment];
    
    }
    
    - (BOOL) isPresentNonFinishedTransaction
    
    {
    
    NSArray *arrTransactions = [ [SKPaymentQueue defaultQueue] transactions];
    
    for(SKPaymentTransaction *transaction in arrTransactions)
    
    {
    
    if([transaction.payment.productIdentifier isEqualToString: strPurchaseId_])
    
    {
    
    if(transaction.transactionState == SKPaymentTransactionStatePurchasing)
    
    {
    
    return YES;
    
    }
    
    else if(transaction.transactionState == SKPaymentTransactionStateFailed)
    
    {
    
    [ [SKPaymentQueue defaultQueue] finishTransaction: transaction];
    
    }
    
    }
    
    }
    
    return NO;
    
    }
    
    #pragma mark ---------------request delegate-----------------
    
    - (void)requestDidFinish:(SKRequest *)request
    
    {
    
    }
    
    - (void)request:(SKRequest *)request didFailWithError:(NSError *)error
    
    {
    
    [delegate_ purchaseController: self didFailLoadProductInfo: error];
    
    }
    

    When getting the products list we check if it is in the products array and the according result is sent as the message to the delegate.

    - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
    
    {
    
    NSArray *invalidIdentifiers = [response invalidProductIdentifiers];
    
    NSArray *products = [response products];
    
    for(SKProduct *product in products)
    
    {
    
    NSString *strId = product.productIdentifier;
    
    if([strId isEqualToString: strPurchaseId_])
    
    {
    
    [delegate_ purchaseController: self didLoadInfo: product];
    
    return;
    
    }
    
    }
    
    if([invalidIdentifiers count])
    
    {
    
    [delegate_ purchaseController: self didFailLoadProductInfo: [NSError errorWithDomain: @"purchaseDomain" code: 143 userInfo: [NSDictionary dictionaryWithObject: @"No Products found" forKey: NSLocalizedDescriptionKey]]];
    
    }
    
    }
    
    #pragma mark -------------------transaction observer-------------------
    

    This method is called for every transaction status change. The product is considered purchased when the transaction status is equal SKPaymentTransactionStatePurchased.

    - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
    
    {
    
    for(SKPaymentTransaction *transaction in transactions)
    
    {
    
    SKPayment *payment = [transaction payment];
    
    if([payment.productIdentifier isEqualToString: strPurchaseId_])
    
    {
    
    if(transaction.transactionState == SKPaymentTransactionStatePurchased)
    
    {
    
    [delegate_ purchaseController: self didFinishPaymentTransaction: transaction];
    
    [queue finishTransaction: transaction];
    
    }
    
    else if(transaction.transactionState == SKPaymentTransactionStateFailed)
    
    {
    
    [delegate_ purchaseController: self didFailPaymentTransactionWithError: [NSError errorWithDomain: @"purchaseDomain" code: 1542 userInfo: nil]];
    
    [queue finishTransaction: transaction];
    
    }
    
    }
    
    }
    
    }
    
    - (void)paymentQueue:(SKPaymentQueue *)queue removedTransactions:(NSArray *)transactions
    
    {
    
    }
    
    - (void) dealloc
    
    {
    
    [ [SKPaymentQueue defaultQueue] removeTransactionObserver: self];
    
    [super dealloc];
    
    }
    
    @end
    

    Creating the Purchase Product and Test User in iTunes Connect

    You should enter ‘Manage Your In App Purchases->Create New’ in iTunes Connect then choose the required application.

    Choose Bundle ID and fill in the information about the Purchase (type, name, price, etc.). You should define ‘Product ID’ that can be of any kind but we advise you to use Reverse DNS. The best name for Product ID is the combination of the Bundle ID of your app and the feature’s name.

    To test the In-App Purchases you should have at least one Test User. Go to ‘Manage Users->In App Purchase Test User’ and choose ‘Add New User’. Fill in the information about the user. Test User e-mail doesn’t have to be real.

    That’s all. Wish you successful testing!

    Hope this information was useful for you. If you’ve got any questions about In App Purchase or about Objective C programming leave it as a reply and we’ll answer it as soon as possible.

    Author: iPhone Development Department.

  96 Responses to “In App Purchase Tutorial”  

  • If an app created a PDF document and required an IAP to email the document to the email recipient of their choosing, how would this be categorized by Apple?

    • Hey TIm

      Were you able to get an answer to your question,
      I have a similar issue. Also did you implement a purchase server? Mind sharing some sample code ?

Leave a Comment

Your email address will not be published. Required fields are marked *