In-App Purchase Tutorial: Getting Started

Learn how to grow app revenue in this in-app purchase tutorial by allowing users to purchase or unlock content or features. By Pietro Rea.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 5 of this article. Click here to view the first page.

Project Configuration

For everything to work correctly, it’s really important that the bundle identifier and product identifiers in the app match the ones you just created in the Developer Center and in App Store Connect.

Head over to the starter project in Xcode. Select the RazeFaces project in the Project navigator, then select it again under Targets. Select the General tab, switch your Team to your correct team, and enter the bundle ID you used earlier.

Enter App Id

Next select the Capabilities tab. Scroll down to In-App Purchase and toggle the switch to ON.

Note: If IAP does not show up in the list, make sure that, in the Accounts section of Xcode preferences, you are logged in with the Apple ID you used to create the app ID.

Open RazeFaceProducts.swift. Notice that there is a placeholder reference to the IAP product you created: SwiftShopping. Replace this with the full Product ID that you configured in App Store Connect — for example:

public static let SwiftShopping = "com.theNameYouPickedEarlier.razefaces.swiftshopping"
Note: The list of product identifiers can be pulled from a web server so new IAPs can be added dynamically rather than requiring an app update. This tutorial keeps things simple and uses hard-coded product identifiers.

Listing In-App Purchases

The store property of RazeFaceProducts is an instance of IAPHelper. As mentioned earlier, this object interacts with the StoreKit API to list and perform purchases. Your first task is to update IAPHelper to retrieve a list of IAPs — there’s only one so far — from Apple’s servers.

Open IAPHelper.swift. At the top of the class, add the following private property:

private let productIdentifiers: Set<ProductIdentifier>

Next, add the following to init(productIds:) before the call to super.init():

productIdentifiers = productIds

An IAPHelper instance is created by passing in a set of product identifiers. This is how RazeFaceProducts creates its store instance.

Next, add these other private properties just under the one you added a moment ago:

private var purchasedProductIdentifiers: Set<ProductIdentifier> = []
private var productsRequest: SKProductsRequest?
private var productsRequestCompletionHandler: ProductsRequestCompletionHandler?

purchasedProductIdentifiers tracks which items have been purchased. The other two properties are used by the SKProductsRequest delegate to perform requests to Apple servers.

Next, still in IAPHelper.swift replace the implementation of requestProducts(_:) with the following:

public func requestProducts(completionHandler: @escaping ProductsRequestCompletionHandler) {
  productsRequest?.cancel()
  productsRequestCompletionHandler = completionHandler

  productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
  productsRequest!.delegate = self
  productsRequest!.start()
}

This code saves the user’s completion handler for future execution. It then creates and initiates a request to Apple via an SKProductsRequest object. There’s one problem: the code declares IAPHelper as the request’s delegate, but it doesn’t yet conform to the SKProductsRequestDelegate protocol.

To fix this, add the following extension to the very end of IAPHelper.swift, after the last curly brace:

// MARK: - SKProductsRequestDelegate

extension IAPHelper: SKProductsRequestDelegate {

  public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
    print("Loaded list of products...")
    let products = response.products
    productsRequestCompletionHandler?(true, products)
    clearRequestAndHandler()

    for p in products {
      print("Found product: \(p.productIdentifier) \(p.localizedTitle) \(p.price.floatValue)")
    }
  }
  
  public func request(_ request: SKRequest, didFailWithError error: Error) {
    print("Failed to load list of products.")
    print("Error: \(error.localizedDescription)")
    productsRequestCompletionHandler?(false, nil)
    clearRequestAndHandler()
  }

  private func clearRequestAndHandler() {
    productsRequest = nil
    productsRequestCompletionHandler = nil
  }
}

This extension is used to get a list of products, their titles, descriptions and prices from Apple’s servers by implementing the two methods required by the SKProductsRequestDelegate protocol.

productsRequest(_:didReceive:) is called when the list is succesfully retrieved. It receives an array of SKProduct objects and passes them to the previously saved completion handler. The handler reloads the table with new data. If a problem occurs, request(_:didFailWithError:) is called. In either case, when the request finishes, both the request and completion handler are cleared with clearRequestAndHandler().

Build and run. Hooray! A list of products (only one so far) is displayed in the table view! It took some work, but you got there in the end.

Note: You can display IAP products on both the iOS simulator as well as physical iOS devices, but if you want to test buying or restoring purchases, you can only do this on physical devices. More on this in the purchasing section below.

 

RazeFaces - Run 2

Still stuck? As you can see, there’s a lot of setting up to do for IAP. Try this tutorial’s comments for a discussion with other readers.

  • Does the project’s Bundle ID match the App ID from the iOS Development Center?
  • Is the full product ID being used when making an SKProductRequest? (Check the productIdentifiers property of RazeFaceProducts.)
  • Is the Paid Applications Contract in effect on iTunes Connect? It can take hours to days for them to go from pending to accepted from them moment you submit them.
  • Have you waited several hours since adding your product to App Store Connect? Product additions may be active immediately or may take some time.
  • Check Apple Developer System Status. Alternatively, try this link. If it doesn’t respond with a status value, then the iTunes sandbox may be down. The status codes are explained in Apple’s Validating Receipts With the App Store documentation.
  • Have IAPs been enabled for the App ID? (Did you select Cleared for Sale earlier?)
  • Have you tried deleting the app from your device and reinstalling it?
Note: If the run was unsuccessful and you didn’t see any products, then there are a number of things to check. This list is courtesy of itsme.manish and abgtan from the forums on earlier versions of this post, plus more tips added over time.
  • Does the project’s Bundle ID match the App ID from the iOS Development Center?
  • Is the full product ID being used when making an SKProductRequest? (Check the productIdentifiers property of RazeFaceProducts.)
  • Is the Paid Applications Contract in effect on iTunes Connect? It can take hours to days for them to go from pending to accepted from them moment you submit them.
  • Have you waited several hours since adding your product to App Store Connect? Product additions may be active immediately or may take some time.
  • Check Apple Developer System Status. Alternatively, try this link. If it doesn’t respond with a status value, then the iTunes sandbox may be down. The status codes are explained in Apple’s Validating Receipts With the App Store documentation.
  • Have IAPs been enabled for the App ID? (Did you select Cleared for Sale earlier?)
  • Have you tried deleting the app from your device and reinstalling it?

Still stuck? As you can see, there’s a lot of setting up to do for IAP. Try this tutorial’s comments for a discussion with other readers.