Building In-App Purchasing into iOS 11 Applications

From Techotopia
Revision as of 15:28, 2 May 2018 by Neil (Talk | contribs)

Jump to: navigation, search


T he 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. While in-app purchasing has been available for a while, iOS 6 introduced the option to host content associated with purchases on Apple’s own servers. 112.1 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, hosted either on the developer’s own server or placed on an Apple hosted server (a concept referred to as App Store hosted content). 112.2 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 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 [[Building In-App Purchasing into iOS 10 Applications |Configuring and Creating App Store Hosted Content for iOS 11 In-App Purchases]].


Contents


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() {

        let request = SKProductsRequest(productIdentifiers: 
      NSSet(objects: "com.ebookfrenzy.consumable") as! Set<String>)
        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:

func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {

    var products = response.products

    if (products.count != 0) {
        // Display the a “buy product” screen containing details
        // from product object
    }

    let invalidProds = response.invalidProductIdentifiers

    for product in invalidProds
    {
         print("Product not found: \(product)")
    }
}

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:

SKPaymentQueue.default().add(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.

let payment = SKPayment(product: product)
SKPaymentQueue.default().add(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 object 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.

func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {

    for transaction in transactions {

        switch transaction.transactionState {

            case SKPaymentTransactionState.purchased:
                if transaction.downloads.count > 0 {
   		 	SKPaymentQueue.default().startDownloads(
				transaction.downloads)
                } else {
			// Unlock feature or content here before  
                    	// finishing transaction
			SKPaymentQueue.default().finishTransaction(
				transaction)
                }

            case SKPaymentTransactionState.failed:                    
		 SKPaymentQueue.default().finishTransaction(
				transaction)

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

func paymentQueue(_ queue: SKPaymentQueue, updatedDownloads downloads: [SKDownload]) {

    for download in downloads
    {
        switch download.downloadState {
            case SKDownloadState.active:
                print("Download progress \(download.progress)")
                print("Download time = \(download.timeRemaining)")
                break
            case SKDownloadState.finished:
                // 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.

Promoting In-App Purchases

Up until the introduction of iOS 11, only iOS apps would appear within the App Store. Now, however, it is also possible to promote in-app purchases within the App Store so that they appear as matching results when users perform searches. In-app purchases are also eligible to appear as recommended items in the App Store, for example as items on the Today page. At the time of writing, each app is limited to 20 promoted in-app purchase items.

If a user selects an in-app purchase from the store and already has the associated app installed, the app is launched and the in-app purchase initiated. If the user does not already have the app installed, the App Store page for the app is displayed so that it can be purchased and installed.

In addition to the changes in iTunes Connect, the following delegate method must be implemented within the class designated as the transaction observer as follows:

func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment 
  payment: SKPayment, for product: SKProduct) -> Bool {
    return true
}

This method is called when the app is launched via the selection of an in-app purchase item in the App Store. If the app returns a true value the in-app purchase is initiated. A false return value cancels the in-app purchase. This can be useful if the app identifies that the user has already purchased the item.

Requesting App Reviews

Another useful StoreKit feature allows the app to request that users provide a review that will appear on the App Store. This can be achieved in a single line of code by making a call to the requestReview method of the SKStoreReviewController class:

SKStoreReviewController.requestReview()

Calling the method does not guarantee that the review request will appear. Instead StoreKit will evaluate whether now is an appropriate time based on factors such as whether or not the user has already reviewed the app, and the amount of time since the app last displayed the request to the user. When displayed, the request will appear as shown in Figure 112-1:


[[File:]]

Figure 112-1


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, Apple provides the option to host the content on Apple’s own servers.