[SWIFT] How to add In-App Purchases in your iOS app

In-App Purchases Swift 3

Introduction

In-app purchase (IAP) is a great way to earn money from your iPhone, iPad, iPod touch or Mac app. You can detect if some app offers IAP by going to the app page, and see if it has “Offers In-App Purchases” or “In-App Purchases” near the Price, Buy, or Get button. IAP is mostly used to unlock some extra content in your app that you want to monetize from. Like it or not, Apple charges 30% for each successful transaction that your app will make.

Examples of IAP:

  • In a photography app you can buy extra effects for x.xx$;
  • Open the Medium iOS app and you can see a “Become a member” button under Settings, which offers a monthly subscription with lots of premium content;
  • Buy coins or other virtual currency in a game, so you can progress faster.

The 4 types of IAP:

  1. Consumable – the user needs to buy these items every time he wants them. Examples of consumable purchases are buying game currency, hints, health etc.
  2. Non-Consumable – once you buy this, you will have it forever. This is a one time purchase, and you can also transfer it between devices connected with the same AppleID. Examples are: upgrading an app to pro version, removing ads, city guide maps etc.
  3. Non-renewing subscriptions – using the app content for a fixed period of time, and you can buy it again after it ends. For example, a sports season.
  4. Auto-renewing subscriptions – you can subscribe to the content or the services that the app is offering for a specific period. It will automatically renew when the period has passed. Examples, newspaper subscriptions, Netflix, games, etc.

With this tutorial, you will learn everything that you need to know about In-App Purchases. I will try to be as much detailed as possible, and will break down this tutorial to multiple steps for better understanding:

  1. iTunes Connect Setup
  2. The Code
  3. Using the Code
  4. Testing

1. iTunes Connect Setup

Enter your bank account details

The most important part to get the In-App Purchases to work is to enter your bank information. That can be done by going to the Agreements, Tax, and Banking section in iTunes Connect. If you don’t have this setup, you can’t use the IAP services. Should look something like this below…

Create a Sandbox User

Then we need to create a Sandbox User. To create this kind of user navigate to Users and Roles and choose the Sandbox Testers section. Remember to use an email that isn’t associated with any Apple ID. You will need the Sandbox User to create test payments, otherwise, you can’t test them.

Create an iTunes App

Go to the My Apps section and create an app. Or, use an existing app if you already have one. To create an app you will have to create an App ID from your Developer account.

Create the In-App Purchases products

Click on your iTunes app and navigate to the Features section. There you can create a new IAP product. Click on the + icon and pick one of the 4 types that I have explained you above. Then enter the required metadata related to that IAP. Pay attention to the Product ID, as you will need that identifier in your app to call the desired IAP. Here is how it should look like.

We are done with the iTunes Connect setup. If you are clear, then let’s proceed to the next step where I will explain you the code.

2. The Code

Keep the code in a separate class. I have named mine IAPHelper, but feel free to change the name if you don’t like it. In this class, we will store everything related to In-App Purchases. For a better understanding, I will attach the whole GIST file and will explain each function in the file…

Recommended for you: Swift 4 — Top 3 new String API Features

Variables

The first thing that you need to do is to create variables from your IAP product ID’s (in my case CONSUMABLE_PURCHASE_PRODUCT_ID and NON_CONSUMABLE_PURCHASE_PRODUCT_ID). Also, we will create variables for handling the IAP request and an array that will store all the available IAP products.

Class functions

  1. canMakePurchases() — returns a boolean value whether the device is able to make purchases or not.
  2. purchaseMyProduct(index: Int) —use this function for initiating a purchase. This function will raise the payment dialog. Send index to get the correct IAP product from the iapProducts array.
  3. restorePurchase() — function for restoring the IAP. Used if the user changes a device, and he already owns a non-consumable IAP in your app.
  4. fetchAvailableProducts() — Create a collection of product ID’s that you want to use, by adding all of them into an NSSet object. Remember to set the delegate method, so you can get the SKProduct results back.

Delegate Methods

  1. productsRequest (_ request:SKProductsRequest, didReceive response:SKProductsResponse) — returns all the available In-App Purchases and populates the iapProducts array. Triggered after calling the fetchAvailableProducts() function.
  2. paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) — handles a situation where a user successfully restores an IAP.
  3. paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) — this delegate method is triggered after calling the purchaseMyProduct(index: Int) functionIn this callback, you will get everything related to the IAP transaction ( like if an item has been purchased or it failed).

Callback handler

As a bonus, I have added the callback handling enum. Create a closure purchaseStatusBlock(), which returns various IAP transaction statuses for more clearer code. To make it even better, I have created an enum type called IAPHandlerAlertType, which will return a message for the suitable case.

//
//  IAPHandler.swift
//
//  Created by Dejan Atanasov on 13/07/2017.
//  Copyright © 2017 Dejan Atanasov. All rights reserved.
//
import UIKit
import StoreKit

enum IAPHandlerAlertType{
    case disabled
    case restored
    case purchased
    
    func message() -> String{
        switch self {
        case .disabled: return "Purchases are disabled in your device!"
        case .restored: return "You've successfully restored your purchase!"
        case .purchased: return "You've successfully bought this purchase!"
        }
    }
}


class IAPHandler: NSObject {
    static let shared = IAPHandler()
    
    let CONSUMABLE_PURCHASE_PRODUCT_ID = "testpurchase"
    let NON_CONSUMABLE_PURCHASE_PRODUCT_ID = "non.consumable"
    
    fileprivate var productID = ""
    fileprivate var productsRequest = SKProductsRequest()
    fileprivate var iapProducts = [SKProduct]()
    
    var purchaseStatusBlock: ((IAPHandlerAlertType) -> Void)?
    
    // MARK: - MAKE PURCHASE OF A PRODUCT
    func canMakePurchases() -> Bool {  return SKPaymentQueue.canMakePayments()  }
    
    func purchaseMyProduct(index: Int){
        if iapProducts.count == 0 { return }
        
        if self.canMakePurchases() {
            let product = iapProducts[index]
            let payment = SKPayment(product: product)
            SKPaymentQueue.default().add(self)
            SKPaymentQueue.default().add(payment)
            
            print("PRODUCT TO PURCHASE: \(product.productIdentifier)")
            productID = product.productIdentifier
        } else {
            purchaseStatusBlock?(.disabled)
        }
    }
    
    // MARK: - RESTORE PURCHASE
    func restorePurchase(){
        SKPaymentQueue.default().add(self)
        SKPaymentQueue.default().restoreCompletedTransactions()
    }
    
    
    // MARK: - FETCH AVAILABLE IAP PRODUCTS
    func fetchAvailableProducts(){
        
        // Put here your IAP Products ID's
        let productIdentifiers = NSSet(objects: CONSUMABLE_PURCHASE_PRODUCT_ID,NON_CONSUMABLE_PURCHASE_PRODUCT_ID
        )
        
        productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers as! Set<String>)
        productsRequest.delegate = self
        productsRequest.start()
    }
}

extension IAPHandler: SKProductsRequestDelegate, SKPaymentTransactionObserver{
    // MARK: - REQUEST IAP PRODUCTS
    func productsRequest (_ request:SKProductsRequest, didReceive response:SKProductsResponse) {

        if (response.products.count > 0) {
            iapProducts = response.products
            for product in iapProducts{
                let numberFormatter = NumberFormatter()
                numberFormatter.formatterBehavior = .behavior10_4
                numberFormatter.numberStyle = .currency
                numberFormatter.locale = product.priceLocale
                let price1Str = numberFormatter.string(from: product.price)
                print(product.localizedDescription + "\nfor just \(price1Str!)")
            }
        }
    }
    
    func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
        purchaseStatusBlock?(.restored)
    }
    
    // MARK:- IAP PAYMENT QUEUE
    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction:AnyObject in transactions {
            if let trans = transaction as? SKPaymentTransaction {
                switch trans.transactionState {
                case .purchased:
                    print("purchased")
                    SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
                    purchaseStatusBlock?(.purchased)
                    break
                    
                case .failed:
                    print("failed")
                    SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
                    break
                case .restored:
                    print("restored")
                    SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
                    break
                    
                default: break
                }}}
    }
}

3. Using the Code

Go to your UIViewController and then in your in viewDidLoad() function, fetch the products and also add the closure where you will get the response of the transaction that the user has made.

Then create an action where you will initialize the transaction window. The below example will get the first product from the iapProducts array.

    override func viewDidLoad() {
        super.viewDidLoad()
        
        IAPHandler.shared.fetchAvailableProducts()
        IAPHandler.shared.purchaseStatusBlock = {[weak self] (type) in
            guard let strongSelf = self else{ return }
            if type == .purchased {
                let alertView = UIAlertController(title: "", message: type.message(), preferredStyle: .alert)
                let action = UIAlertAction(title: "OK", style: .default, handler: { (alert) in
                    
                })
                alertView.addAction(action)
                strongSelf.present(alertView, animated: true, completion: nil)
            }
        }
    }
    
    @IBAction func consumable(btn: UIButton){
        IAPHandler.shared.purchaseMyProduct(index: 0)
    }

4. Testing

Sign In with your Sandbox User on your iOS device, navigate to your app and click the action to initiate the transaction. Don’t worry about the price on the transaction window. Since you are using a Sandbox User, nothing will be charged from your account.

NOTE: You can’t test In-App Purchases on iOS Simulator. Please use a real device.

Recommended for you: I ❤ Swift [Part 1] — UIViewController & Extensions


That’s it from this tutorial, hopefully, it helped you to learn how to add In-App Purchases in your iOS app. Subscribe below for more interesting Swift tutorials. Also, don’t forget to hit share so other people can find it.

Leave a Reply

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