iOS In-App Purchase with Swift 5 – XSTutorials

iOS In-App Purchase with Swift 5

This is a simple tutorial for you to learn how to implement In-App Purchase for Consumable and Non-Consumable products in your iOS app using Swift 5. A Consumable product is used once, after which it becomes depleted and can be purchased again, while a Non-Consumable product is purchased once and does not expire or decrease with use.

PLEASE NOTE: This tutorial has been written using XCode 10 and Swift 5

App Store Connect settings

I assume you already have an active Apple Developer account and your Paid Apps agreement is Active, as shown in this example:

The Agreements, Tax & Banking section

In this way, you’ll be able to implement In-App Purchases in your iOS application and get revenue from sales.

Let’s start from the App Store Connect page of your app. I won’t get into details about how to prepare an app for review, this article covers that process, so let’s pretend you already have created an App in the My Apps section.

Enter the Features section, click the In-App Purchases link and the (+) button. A popup will appear showing the IAP options. For the sake of this tutorial, I’ll cover the Consumable and Non-Consumable options only, so select the Consumable one and click the Create button.

The In-App Purchases page

In the next page, fill the following fields:

  • Reference Name – The reference name will be used on App Store Connect and in Sales and Trends reports. It won’t be displayed on the App Store. The name can’t be longer than 64 characters.
  • Product ID – A unique alphanumeric ID that will be used for reporting, something like com.iap.unlockpro
  • Price – select the desired price tier from the dropdown list
  • Display Name – The name of the IAP product that will be displayed on the App Store.
  • Description – The text you’ll type in this field will be displayed in the app
  • Screenshot – Upload a screenshot of your IAP, The Apple reviewer will use it just for review purposes, it won’t be displayed on the App Store.
  • Review notes – Additional information about your In-App purchase that could help us with our review.

When you’re done, click the Save button. Here’s an example:

Create a Consumable product

Now let’s create a Non-Consumable product, so click In-App Purchases from the left menu to go back to the main page, then click the (+) button again, select Non-Consumable and click the Create button.

Select a Non-Consumable product

As you’ve done for the Consumable product, fill all the necessary fields in the New In-App Purchase page and click the Save button to create your Non-Consumable product. Here’s an example:

Create a Non-Consumable product

Now click App Store -> Prepare for Submission, scroll down to the In-App Purchases section, click the (+) button, select your IAP products and click the Done button.

Add your IAP products to your application

Click the Save button to get this job done. It’s time to build the code for your In-App Purchases and test it out.

The Code

Open your Xcode project and add a IAP Controller and its relative Swift file – name it as “IAPController”. Here’s an example of how this ViewController may look like in the Storyboard:

In-App Purchase Controller design

As you can see, I’ve placed a button to buy the Consumable product (100 Conis Chest), a button to purchase the Non-Consumable product (Unlock Pro Version) and a Restore Purchase button – this is required in case of Non-Consumable purchase only.

We need to connect those Buttons to our IAPController.swift file as IBOutlets and IBActions.

@IBOutlet weak var buy100coinsButton: UIButton!
@IBOutlet weak var unlockProButton: UIButton!
@IBOutlet weak var restorePurchaseButton: UIButton!

@IBAction func buy100CoinsButt(_ sender: UIButton) {
}

@IBAction func unlockProButt(_ sender: UIButton) {
}

@IBAction func restorePurchaseButt(_ sender: UIButton) {
}

Now that we’re all set, let’s start coding. First of all, enter the Capabilities tab in Xcode and switch the In-App Purchase option to ON. Xcode will automatically link the StoreKit.franework to your project and add the IAP feature to your App ID.

Enable In-App Purchase in Xcode

Select the IAPController.swift file to show it in the Editor area. Import the StoreKit framework on the top of it.

import StoreKit

Add the StoreKit delegate protocols to the Class declaration:

SKProductsRequestDelegate, SKPaymentTransactionObserver

Declare the needed variables for our project inside the Class, above the viewDidLoad() function:

var productsRequest = SKProductsRequest()
var validProducts = [SKProduct]()
var productIndex = 0

Inside the viewDidLoad() method, hide the 2 purchase buttons and call a function:

buy100coinsButton.isHidden = true
unlockProButton.isHidden = true

fetchAvailableProducts()

Inside the Class, below the viewDidLoad() function, paste this one:

func fetchAvailableProducts()  {
    let productIdentifiers = NSSet(objects:
        "com.iap.100coins",         // 0
        "com.iap.unlockproversion"  // 1
    )
    
    productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers as! Set<String>)
    productsRequest.delegate = self
    productsRequest.start()
}

This function searches for IAP products you’ve registered in the App Store Connect page of your app and calls the SKProductsRequestDelegate .

Now we need to get the products request’s data, so use this code:

func productsRequest (_ request:SKProductsRequest, didReceive response:SKProductsResponse) {
    if (response.products.count > 0) {
        validProducts = response.products
            
        let prod100coins = response.products[0] as SKProduct
        let prodUnlockPro = response.products[1] as SKProduct
        print("1st rpoduct: " + prod100coins.localizedDescription)
        print("2nd product: " + prodUnlockPro.localizedDescription)

        buy100coinsButton.isHidden = false
        unlockProButton.isHidden = false
    }
}

The function above will get IAP products info from the App Store Connect website and print their description in the Xcode console. In this case, here’s what the console will print:

1st rpoduct: Get 100 Coins
2nd product: Unlock all features of this app

This is because we’ve created the 100 Coins Chest product first, then the Unlock pro one, and we also declared their product IDs in the fetchAvailableProducts() function in that order.

Let’s now add a StoreKit delegate function that’s required by Apple since 2018:

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

We also have to check if purchases are enabled on our device, so paste this code below that function:

func canMakePurchases() -> Bool {  return SKPaymentQueue.canMakePayments()  }

Now let’s add the functions that perform a purchase:

func purchaseMyProduct(_ product: SKProduct) {
    if self.canMakePurchases() {
        let payment = SKPayment(product: product)
        SKPaymentQueue.default().add(self)
        SKPaymentQueue.default().add(payment)
    } else { print("Purchases are disabled in your device!") }
}

It’s time to check the purchase transactions and perform the needed operations. Add this function in your Class:

func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction:AnyObject in transactions {
            if let trans:SKPaymentTransaction = transaction as? SKPaymentTransaction {
                switch trans.transactionState {
                    
                case .purchased:
                    SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
                    if productIndex == 0 {
                        print("You've bought 100 coins!")
                        buy100coinsButton.setTitle("Buy another 100 Coins Chest", for: .normal)
                    } else {
                        print("You've unlocked the Pro version!")
                        unlockProButton.isEnabled = false
                        unlockProButton.setTitle("PRO version purchased", for: .normal)
                    }
                    break
                    
                case .failed:
                    SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
                    print("Payment has failed.")
                    break
                case .restored:
                    SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
                    print("Purchase has been successfully restored!")
                    break
                    
                default: break
        }}}
}

Let’s paste these two functions to restore a purchase:

func restorePurchase() {
        SKPaymentQueue.default().add(self as SKPaymentTransactionObserver)
        SKPaymentQueue.default().restoreCompletedTransactions()
}
    
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
        print("The Payment was successfull!")
}

We are missing the code for the 3 IBActions we’ve placed in our Swift file. Here’s what you have to do:

@IBAction func buy100CoinsButt(_ sender: UIButton) {
    productIndex = 0
    purchaseMyProduct(validProducts[productIndex])
}
    
@IBAction func unlockProButt(_ sender: UIButton) {
    productIndex = 1
    purchaseMyProduct(validProducts[productIndex])
}
    
@IBAction func restorePurchaseButt(_ sender: UIButton) {
    restorePurchase()
}

You’re done coding and ready to test your In-App Purchases!

Remember to sign in as a Sandbox user in Settings -> iTunes & App Stores -> SANDBOX ACCOUNT on your device:

Sandbox Account on your device

If you run your app now and tap the Buy 100 Coins Chest button, an alert with a TextField should appear, asking you to type the password of your Sandbox account. Type it and click the Buy button. Wait for the app to connect to the App Store and process your payment. When it’s done, you should get an alert like this:

100 Coins Chest purchase

And the console should print out this message:

You've bought 100 coins!

The first purchase button’s title will change into “Buy another 100 Coins Chest”, as we set in the code, so you can repeat the purchase again since this is a Consumable IAP. Try it again and see what happens 😉

Then, let’s test the “Unlock Pro version” button. If you tap it, an alert like this should popup – it won’t ask you for the password again if you’re making the purchase within 15 minutes form your last one.

Unlock Pro version purchase

Click Buy and wait for the successful payment’s alert. Then the button’s title should change into “PRO version purchased”, and it should get disabled, because this was a Non-Consumable purchase. The console should print the following message:

You've unlocked the Pro version!

Now, you can kill the app and open it again. Try tapping the “Restore Purchase” button and wait for the Xcode console to print the following messages:

Purchase has been successfully restored!
The Payment was successfull!

Here is the full code of our IAPController.swift file, just to put all things together:

import UIKit
import StoreKit

class ViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver {
   
    @IBOutlet weak var buy100coinsButton: UIButton!
    @IBOutlet weak var unlockProButton: UIButton!
    @IBOutlet weak var restorePurchaseButton: UIButton!
    
    
    var productsRequest = SKProductsRequest()
    var validProducts = [SKProduct]()
    var productIndex = 0
    
    
    // viewDidLoad()
    override func viewDidLoad() {
        super.viewDidLoad()
    
        
        buy100coinsButton.isHidden = true
        unlockProButton.isHidden = true
        
        fetchAvailableProducts()
    }
    
    
    
    func fetchAvailableProducts()  {
        let productIdentifiers = NSSet(objects:
            "com.iap.100coins",         // 0
            "com.iap.unlockproversion"  // 1
        )
        productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers as! Set<String>)
        productsRequest.delegate = self
        productsRequest.start()
    }
    
    
    
    func productsRequest (_ request:SKProductsRequest, didReceive response:SKProductsResponse) {
        if (response.products.count > 0) {
            validProducts = response.products
            
            // 1st IAP Product
            let prod100coins = response.products[0] as SKProduct
            let prodUnlockPro = response.products[1] as SKProduct
            print("1st rpoduct: " + prod100coins.localizedDescription)
            print("2nd product: " + prodUnlockPro.localizedDescription)
            
            buy100coinsButton.isHidden = false
            unlockProButton.isHidden = false
        }
    }
    
    
    func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool {
        return true
    }
    
    
    func canMakePurchases() -> Bool {  return SKPaymentQueue.canMakePayments()  }
    
    
    func purchaseMyProduct(_ product: SKProduct) {
        if self.canMakePurchases() {
            let payment = SKPayment(product: product)
            SKPaymentQueue.default().add(self)
            SKPaymentQueue.default().add(payment)
        } else { print("Purchases are disabled in your device!") }
    }
    
    
    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction:AnyObject in transactions {
            if let trans:SKPaymentTransaction = transaction as? SKPaymentTransaction {
                switch trans.transactionState {
                    
                case .purchased:
                    SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
                    if productIndex == 0 {
                        print("You've bought 100 coins!")
                        buy100coinsButton.setTitle("Buy another 100 Coins Chest", for: .normal)
                    } else {
                        print("You've unlocked the Pro version!")
                        unlockProButton.isEnabled = false
                        unlockProButton.setTitle("PRO version purchased", for: .normal)
                    }
                    break
                    
                case .failed:
                    SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
                    print("Payment has failed.")
                    break
                case .restored:
                    SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
                    print("Purchase has been successfully restored!")
                    break
                    
                default: break
        }}}
    }
    
    
    
    func restorePurchase() {
        SKPaymentQueue.default().add(self as SKPaymentTransactionObserver)
        SKPaymentQueue.default().restoreCompletedTransactions()
    }
    
    func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
        print("The Payment was successfull!")
    }
    
    
    
    // Buttons -------------------------------------
    @IBAction func buy100CoinsButt(_ sender: UIButton) {
        productIndex = 0
        purchaseMyProduct(validProducts[productIndex])
    }
    
    @IBAction func unlockProButt(_ sender: UIButton) {
        productIndex = 1
        purchaseMyProduct(validProducts[productIndex])
    }
    
    
    @IBAction func restorePurchaseButt(_ sender: UIButton) {
        restorePurchase()
    }
    
    
}// ./ end

Conclusion

That’s all for this tutorial, you have learned how to prepare In-App Purchase products on the App Store Connect website and code them in Xcode.

Hope you enjoyed this article, feel free to post comments about it. 

Buy me a coffee - XScoder - thanks for your support
Your support will be highly appreciated 😉