Technical Note TN2387

In-App Purchase Best Practices

This document describes best practices for implementing in-app purchase in iOS, macOS, and tvOS applications.

Best Practices
Additional Resources
Document Revision History

Best Practices

Below is a list of best practices that developers should follow when implementing in-app purchase.

Add a transaction queue observer at application launch

StoreKit attaches your observer to the payment queue when your app calls

SKPaymentQueue.default().add(your_observer)

StoreKit automatically notifies your observer when the content of the payment queue changes upon resuming or while running your app. Adding your app's observer at launch ensures that it will persist during all launches of your app, thus allowing your app to receive all the payment queue notifications.

Consider the case of an app whose DetailViewController class creates an observer right before adding a payment request to the queue as seen in Listing 1. This observer exists as long as the instance of DetailViewController, which created it, exists. In the advent of interruptions such as a network failure, the app does not complete the purchase process, and the associated transaction stays in the payment queue. When the app resumes, it has no observers as the above observer was deallocated when your app was sent to the background. As a result, your app would not get notified about the transaction in the queue.

Listing 1  Does not follow best practices for implementing a transaction observer: App adds an observer to the payment queue when the customer attempts to purchase a product.

import UIKit
import StoreKit
 
class DetailViewController: UIViewController, SKPaymentTransactionObserver {
                            ....
    // Called when a customer attempts to purchase a product.
    @IBAction func purchase(_ sender: UIButton) {
        // Register an observer to the payment queue.
        SKPaymentQueue.default().add(your_observer)
 
        // Create a payment request.
        let payment = SKMutablePayment(product: product)
 
        // Submit the payment request to the payment queue.
        SKPaymentQueue.default().add(payment)
    }
                           ....
}

See Listing 2 for an example that correctly registers a transaction queue observer.

Listing 2  Follows best practices for registering a transaction observer.

import UIKit
import StoreKit
 
class AppDelegate: UIResponder, UIApplicationDelegate {
                           ....
    // Attach an observer to the payment queue.
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Attach an observer to the payment queue.
        SKPaymentQueue.default().add(your_observer)
        return true
    }
 
    // Called when the application is about to terminate.
    func applicationWillTerminate(_ application: UIApplication) {
       // Remove the observer.
       SKPaymentQueue.default().remove(your_observer)
    }
                           ....
}

StoreKit removes an observer from the payment queue when your app calls

 SKPaymentQueue.default().remove(your_observer)

As such, StoreKit may attempt to notify the above observer if it was not removed from the payment queue, thus causing your app to crash, as the observer no longer exists.

Query the App Store for product information before presenting your app’s store UI

Your app must first send a product request to the App Store before deciding what products to display for purchase in its user interface. Sending a product request allows you to determine whether your products are available for sale in the App Store, thus preventing you from displaying products that cannot be purchased in your app. See Retrieving Product Information to learn how to create a product request. The App Store responds to your product request with an SKResponse object. Use its products property to update your UI, thus ensuring that your customers will only be presented with products available for sale in the App Store.

Listing 3  Does not follow best practices for presenting In-App Purchase products: App queries the App Store for product information after presenting it for sale.

import UIKit
import StoreKit
 
class DetailViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver {
    var productRequest: SKProductsRequest!
                            ....
    // App first displays a product for sale, then queries the App Store about it when
   //  a customer attempts to purchase it.
   @IBAction func purchase(_ sender: UIButton) {
        // Create a set for your product identifier.
        let identifiers = Set(your_product_identifier)
 
        // Initialize the product request with the above set.
        productRequest = SKProductsRequest(productIdentifiers: identifiers)
        productRequest.delegate = self
 
        // Send the request to the App Store.
        productRequest.start()
    }
 
    // Get the App Store's response.
    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        // Let's sell the first available product.
        let product = response.products.first!
        let payment = SKMutablePayment(product: product)
        SKPaymentQueue.default().add(payment)
    }
}

See Listing 4 for an example that follows best practices for presenting in-app purchase products.

Listing 4  Follows best practices for presenting In-App Purchase products.

import UIKit
import StoreKit
 
class DetailViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver {
    var productRequest: SKProductsRequest!
 
    // Fetch information about your products from the App Store.
    func fetchProducts(matchingIdentifiers identifiers: [String]) {
        // Create a set for your product identifiers.
        let productIdentifiers = Set(identifiers)
        // Initialize the product request with the above set.
        productRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
        productRequest.delegate = self
 
        // Send the request to the App Store.
        productRequest.start()
    }
 
    // Get the App Store's response
    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
         // No purchase will take place if there are no products available for sale.
        // As a result, StoreKit won't prompt your customer to authenticate their purchase.
        if response.products.count > 0 {
            // Use availableProducts to populate your UI.
            let availableProducts = response.products
        }
    }
}

Provide a UI for restoring products

If your app sells non-consumable, auto-renewable subscription, or non-renewing subscription products, then you must provide a UI that allows them to be restored. See Differences Between Product Types and Restoring Purchased Products for more information.

Process the transaction

StoreKit adds a payment request to the payment queue when your app calls

SKPaymentQueue.default().add(your_payment)

The queue creates a transaction object to process this request. StoreKit notifies your observer by calling its paymentQueue(_:updatedTransactions:) method when the state of this transaction changes. Every transaction has five possible states as shown in In-App Purchase Programming Guide> Delivering Products> Table 4-1 Transaction statuses and corresponding actions. Make sure that your observer's paymentQueue(_:updatedTransactions:) can respond to any of these states at any time. Implement the paymentQueue(_:updatedDownloads:) method on your observer, if your app provides products hosted by Apple.

Provide the paid content

Deliver the content or unlock your app's functionality when your app receives a transaction whose state is purchased or restored. These states indicate that a payment was received for the product being sold. As such, your customer expects to be provided with the paid content. If your purchased product includes hosted content from the App Store, be sure to call SKPaymentQueue's start(_:) to download the content. See Unlocking App Functionality and Delivering Associated Content for more information.

Finish the transaction

Transactions stay in the payment queue until they are removed. StoreKit will call your observer’s paymentQueue(_:updatedTransactions:) every time that your app launches or resumes from background until they are removed. To that effect, your customers may be repeatedly asked to authenticate their purchases or be prevented from purchasing your products.

Call finishTransaction(_:) on your transaction to remove it from the queue. Finished transactions are not recoverable. Therefore, be sure to provide your content, to download all Apple-hosted content of a product, or complete your purchase process before finishing your transaction. See Finishing the Transaction for more information.

Test your implementation of In-App Purchase

Be sure to thoroughly test your implementation of in-app purchase before submitting your app for review. See Suggested Testing Steps for various scenarios for testing your implementation and TN2413: In-App Purchase FAQ to troubleshoot your issues.

Always verify your receipt with the production URL first

If you are doing receipt validation, be sure to verify your receipt with the production URL (https://buy.itunes.apple.com/verifyReceipt) first. This applies even in the case where your app is used in the sandbox environment. App Review will review the production version of your app in the sandbox. When your app processes the receipt, it must be capable of detecting the 21007 receipt status code and sending the receipt to the sandbox receipt validation server (https://sandbox.itunes.apple.com/verifyReceipt). Once your app is approved and running in the production environment, sending the receipt to the production server first is the correct action. See What url should I use to verify my receipt? for more information.

Additional Resources



Document Revision History


DateNotes
2017-05-02

Fixed typos.

2016-11-09

Editorial update.

2015-12-07

Added the "Always verify your receipt with the production URL first" section.

2014-12-16

Updated the "Add a transaction queue observer at application launch" section.

2014-08-05

New document that provides best practices for implementing in-app purchase in iOS, macOS, and tvOS applications.