Building In-App Purchasing into iPhone iOS 6 Applications

Revision as of 19:38, 10 May 2016 by Neil (Talk | contribs) (Text replacement - "<hr> <table border="0" cellspacing="0"> <tr>" to "<!-- Ezoic - BottomOfPage - bottom_of_page --> <div id="ezoic-pub-ad-placeholder-114"></div> <!-- End Ezoic - BottomOfPage - bottom_of_page --> <hr> <table border="0" cellspacing="0"> <tr>")

Revision as of 19:38, 10 May 2016 by Neil (Talk | contribs) (Text replacement - "<hr> <table border="0" cellspacing="0"> <tr>" to "<!-- Ezoic - BottomOfPage - bottom_of_page --> <div id="ezoic-pub-ad-placeholder-114"></div> <!-- End Ezoic - BottomOfPage - bottom_of_page --> <hr> <table border="0" cellspacing="0"> <tr>")

PreviousTable of ContentsNext
Making Store Purchases with the SKStoreProductViewController ClassPreparing an iOS 6 Application for In-App Purchases


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book


The previous chapter explored the mechanism for allowing users to purchase items from Apple’s iTunes, App and iBooks stores from within an application. A far more common requirement for application developers, however, is the need to generate revenue by charging users to unlock additional functionality or to gain access to some form of premium content. This is a concept referred to as “In-App Purchase” (IAP) and is made possible by a collection of classes within the Store Kit Framework combined with settings configured using the iTunes Connect web portal.

Whilst in-app purchasing has been available for a while, iOS 6 now introduces the option to host content associated with purchases on Apple’s own servers.

In-App Purchase Options

In-app purchase allows digital or virtual items to be purchased from within applications. These typically take the form of additional content (such as an eBook or digital magazine edition), access to more advanced levels in a game or the ability to buy virtual goods or property (such as crop seeds in a farming game).

Purchases fall into the categories of consumable (the item must be purchased each time it is required by the user such as virtual currency in a game), non-consumable (only needs to be purchased once by the user such as content access) and subscription based.

In the case of subscription based purchases, these can be non-renewing (item remains active for a specified amount of time), auto-renewing (the subscription is renewed automatically at specified intervals until cancelled by the user) or free subscription based (allowing free access to content for Newsstand based applications). Non-consumable and auto-renewable purchases should be configured such that they can be restored, at the request of the user, on any of the user’s iOS devices.

The item purchased by the user can either be built-in or server based. The term “built-in” refers to content or functionality that is already present in the application, but currently locked in some way. Once the purchase has been made, the functionality is unlocked. Server based purchases involve downloading of the item from a remote server. This might, for example, be a new background image for a game or a file containing additional content.

Prior to iOS 6, it was the responsibility of the application developer to set up, configure and maintain a server system for server based in-app purchases. With the introduction of iOS 6, however, the downloadable content can be hosted either on the developer’s own server or placed on an Apple hosted server (a concept referred to as App Store hosted content).

Uploading App Store Hosted Content

In the event that a purchase involves the download of additional content hosted on the App Store, this content must first be uploaded. The files containing the content are placed in a structured folder. The root level of the folder must contain a ContentInfo.plist XML file that contains two keys:

  • ContentVersion – The version number of the content.
  • IAPProductIdentifier – The product identifier of the product with which the content is associated

The actual content files must be placed in a subfolder named Contents. In practice, much of the work involved in packaging and uploading content can be performed using either Xcode or the Application Loader tool as outlined in the chapter entitled Configuring and Creating App Store Hosted Content for iOS 6 In-App Purchases.


Configuring In-App Purchase Items

The application developer is responsible for defining the products that are available for purchase. This involves assigning product identifiers, descriptions and pricing to each item and is performed using iTunes Connect.

Sending a Product Request

When the user needs to purchase one or more items, the application uses the Store Kit Framework to send a product request. The request is packaged in the form of an SKProductRequest object configured with product identifiers of one or more items to be purchased. The application should also check whether or not the user has used the Settings app to disable in-app purchases on the device before initiating the purchase:

if ([SKPaymentQueue canMakePayments])
{
    SKProductsRequest *request = [[SKProductsRequest alloc]
        initWithProductIdentifiers:
        [NSSet setWithObject:@"com.ebookfrenzy.consumable1"]];
        
    request.delegate = self;

    [request start];
}
else
{
     // Tell user that In-App Purchase is disabled in Settings
}

When the App Store has located and returned the product information, the Store Kit Framework calls the application’s didReceiveResponse: delegate method passing it an object of type SKProductsResponse. This object contains an SKProduct object for each product that matched the request, along with a list of any product items for which a match could not be found in the store. If, on the other hand, the Store Kit was unable to contact the App Store, the didFailWithError: delegate method will be called to notify the application:

-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
    NSArray *products = response.products;

    for (SKProduct *product in products)
    {
        // Display the a “buy product” screen containing details
        // from product object
    }

    products = response.invalidProductIdentifiers;

    for (SKProduct *product in products)
    {
        // Handle invalid product IDs if required
    }
}

Each SKProduct object will contain product information (name, description and price) and a Boolean property indicating whether the product has associated with it downloadable content hosted on the App Store.

There is no SKStoreProductViewController equivalent for in-app purchasing. It is the responsibility of the application, therefore, to create and display a view where the user can review the product details (which should be extracted from the SKProduct object rather than hard coded into the application code) and initiate or cancel the purchase.

Accessing the Payment Queue

In order to process the purchase in the event that the user decides to buy the product, it will be necessary for the application to place requests on the application’s payment queue. The SKPaymentQueue instance is not an object that is created by the application, but rather an existing object on which the application makes method calls. One such method call must be made in order to assign a transaction observer object to the queue:

- (void)viewDidLoad
{
    [super viewDidLoad];
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}

Since the payment queue continues to process requests independently of the application (and even in the event that the application exits) it is recommended that access to the payment queue and transaction observer object assignment takes place when the application is first launched. This will ensure that the application will be notified immediately of any payment transactions that were completed after the application last exited.

The Transaction Observer Object

The transaction observer object assigned to the payment queue can be an instance of any class within the application that implements the SKPaymentTransactionObserver protocol. Compliance with the protocol involves, at a minimum, implementing the updatedTransactions: method. If the application is required to download App Store hosted content, the updatedDownloads: method also needs to be implemented.

In the event that downloads are to be performed, the updatedDownloads: method will be called at regular intervals, providing status updates on the download progress.

Initiating the Purchase

In order to process the purchase, the application creates a payment request in the form of an SKPayment object containing the matching SKProduct object for the item (this is usually just a case of using the object passed through to the productsRequest: method). The payment request is then placed into the SKPaymentQueue which is then responsible for communicating with the App Store to process the purchase.

SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];

The Transaction Process

The payment queue will call the updatedTransactions: method on the observer object when the purchase is complete, passing through as an argument an array of SKPaymentTransaction objects (one for each item purchased). The method will need to check the transactionState property of each transaction object to verify the success or otherwise of the payment and, in the event of a successful transaction, either download or unlock the purchased content or feature.

Each SKPaymentTransaction objection contains a downloads property. This is an array containing an entry for each content package hosted on the App Store server that needs to be downloaded as part of the purchase. In the event that the purchase requires content downloads, these downloads can be initiated via a call to the startDownloads: method of the SKPaymentQueue instance.

-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    for (SKPaymentTransaction *transaction in transactions)
    {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchased:
               if (transaction.downloads)
   		{
       		    [[SKPaymentQueue defaultQueue]
 			startDownloads:transaction.downloads];
               } else {
                    // Unlock feature or content here before finishing 
                    // transaction
	            [[SKPaymentQueue defaultQueue] 
                          finishTransaction:transaction];
               }
               break;

            case SKPaymentTransactionStateFailed:
                [[SKPaymentQueue defaultQueue] 
                          finishTransaction:transaction];
                break;

            default:
                break;
        }
    }
}

Once started, the system will perform any necessary downloads in the background. As the download progresses, the updatedDownloads: method of the observer object will be called at regular intervals. Each time the method is called, it will be passed an array of SKDownload objects (one for each download currently in progress) containing information such as download progress, estimated time to completion and state.

Download state can be one of a number of values including active, waiting, finished, failed, paused or cancelled. When the download is completed, the file URL of the downloaded file is accessible via the contentURL property of the SKDownload object. At this point, the file is in temporary cache so should be moved to a permanent location (such as the Documents directory).

-(void)paymentQueue:(SKPaymentQueue *)queue updatedDownloads:(NSArray *)downloads
{
    for (SKDownload *download in downloads)
    {
        switch (download.downloadState) {
            case SKDownloadStateActive:
                NSLog(@"Download progress = %f", 
                      download.progress);
                NSLog(@"Download time = %f", 
                      download.timeRemaining);
                break;
            case SKDownloadStateFinished:
                // Download is complete. Content file URL is at 
                // path referenced by download.contentURL. Move                       
                // it somewhere safe, unpack it and give the user 
                // access to it
                 break;
            default:
                 break;
        }
    }
}

Finally, for each transaction returned by the payment queue, and after the purchased feature or content access has been granted to the user, the application must call the queue’s finishTransaction: method in order to complete the transaction. In the case of content downloads, any downloaded files must be moved from temporary cache to a safe location before this method call is made.

The hosted content package is downloaded in the form of a Zip file which may then be unpacked using one of a variety of freely available source code packages for iOS. One such package is SSZipArchive, details of which can be found at:

https://github.com/samsoffes/ssziparchive

Transaction Restoration Process

As previously mentioned, purchases that fall into the categories of non-consumable or auto-renewable subscription should be made available on any other iOS devices on which the user has installed the corresponding application. This can be achieved using the transaction restoration feature of the Store Kit Framework.

In order to restore transactions, the application makes a call to the restoreCompletedTransactions: method of the payment queue. The queue will then contact the App Store and return to the application (via the observer object) an array of SKPaymentTransaction objects for any previous purchases. Each object will contain a copy of the original transaction object which is accessible via the originalTransaction property. This may then be used to reactivate the purchase within the application and, if appropriate, download any associated content files hosted on the App Store.

It is important to be aware that restoration should not be performed automatically at application launch time. This should, instead, be implemented by providing the user with an option within the application to restore previous purchases.

Testing In-App Purchases

Clearly it would be less than ideal if the only way to test in-app purchasing was to spend real money to purchase the items. Fortunately, Apple provides what it calls a “sandbox” environment via which purchasing can be tested without spending any money.

By default, applications signed with a development certificate will automatically connect to the sandbox when purchases are made.

In order for the sandbox to be operable, it is necessary to create a test user using iTunes Connect and set up the products that are to be made available for in-app purchase.

Summary

In-app purchases present the application developer with additional sources of revenue by charging the user for content or to unlock additional features from within an application. In-app purchases can be consumable, non-consumable or subscription-based.

The item purchased by the user can be built-in, in that the functionality is unlocked internally within the application, or server based, whereby content is downloaded from a server. In terms of server based purchases, iOS 6 now provides the option to host the content on Apple’s own servers.


Learn SwiftUI and take your iOS Development to the Next Level
SwiftUI Essentials – iOS 16 Edition book is now available in Print ($39.99) and eBook ($29.99) editions. Learn more...

Buy Print Preview Book



PreviousTable of ContentsNext
Making Store Purchases with the SKStoreProductViewController ClassPreparing an iOS 6 Application for In-App Purchases