DEV Community

Cover image for In-App Purchase Implementation in iOS
Abhinand R K
Abhinand R K

Posted on

In-App Purchase Implementation in iOS

In this blog post i will explain how to integrate In-App purchase in iOS App.

Overview

An in-app purchase (or IAP) allows developers to charge users for specific functionality or content while using an app. Implementing IAPs is particularly compelling for several reasons:

  1. It’s an extra way to earn money, in addition to simply selling the app for a fee upfront. Some users are willing to spend a lot more on extra content or features.
  2. An app can be offered for free, which makes it a no-brainer download for most people. Free apps will typically get many more downloads than paid apps. If users enjoy the app, then they can purchase more content or functionality later.
  3. You can display advertisements to the user in a free app with an option to remove them by purchasing an IAP.

You can only offer In-App Purchases for digital items(eg: Byju's,Netflix), and not for physical goods or services(eg: Amazon, Flipkart).

What is IAP?

According to Apple, In-App Purchase allows you to offer users the opportunity to purchase in-app content and features. Purchases can be made within your app, or directly from the App Store. The StoreKit framework connects to the App Store on your app’s behalf to prompt for and securely process payments. The framework then notifies your app, which delivers the purchased products. To validate purchases, you can verify receipts on your server with the App Store or on the device.

Different components in In-App Purchase flow

Types of IAP

Consumable: It’s when you buy something to progress in your app, for example, life in a game. You basically buy it once, use it, and afterward it is possible to buy it again.

Non-consumable: Users buy these items one time, can be used in the future for free. On reinstalling, changing devices these products will not be lost. If the user loses, might be able to download it again for free by restoring in-app purchases. For example: upgrading the app to the pro version, removing ads, etc.

Non-renewing subscriptions: The user will be able to use these items for a fixed period of time, these items can be purchase again after the subscription end. For example: a sport session pass for one, three, or six months.

Auto-renewable subscriptions: The user can buy these item for a specified period of time, It’ll automatically renew when the period has passed. For example: Ongoing services (Netflix, Hulu Plus, etc.), Magazine subscriptions etc.

In this blog we will be implementing Non-renewing subscriptions

Creating In-App Purchase Products

When offering IAPs you must first add an entry for each individual purchase within App Store Connect. If you’ve ever listed an app for sale in the store, it’s a similar process and includes things like choosing a pricing tier for the purchase. When the user makes a purchase, the App Store handles the complex process of charging the user and replies with data about such operation.

Now, while viewing your app’s entry in App Store Connect, click on the Features tab and then select In-App Purchases. To add a new IAP product, click the + to the right of In-App Purchases.

Create In-App Purchase product

You’ll see the following dialog appear:

Different types of In-App purchase products

Next, fill out the details for the IAP as follows

  • Reference Name: A nickname identifying the IAP within iTunes Connect. This name does not appear anywhere in the app. The title of the RazeFace you’ll be unlocking with this purchase is Swift Shopping, so enter that here.
  • Product ID: This is a unique string identifying the IAP. Usually it’s best to start with the Bundle ID and then append a unique name specific to this purchasable item. For example, you can use: com.iq.colearn.swiftshopping.
  • Cleared for Sale: Enables or disables the sale of the IAP. You want to enable it!

Price Tier: The cost of the IAP.

App Store Connect may complain that you’re missing metadata for your IAP. Before you submit your app for review, you’re required to add a screenshot of the IAP at the bottom of this page. The screenshot is used only for Apple’s review and does not appear in your App Store listing.

Sandbox User Creation

Now, we need to create a sandbox user, to create navigate to Users and Roles in iTunes connect account and choose the Sandbox Testers section. It is very important to add sandbox user to test the IAP services, by sandbox user you can make transactions for free.

Sandbox user creation

Always remember to use an email that is not associated with any Apple ID and also it should be a valid email id.

Project Configuration

Head over to the Capabilities tab and scroll down to In-App Purchase and toggle the switch to ON.

Toggle in-App purchase capability in Project configuration section in Xcode

The Code in Swift

We will be using the StoreKit framework provided by Apple which is surprisingly easy to implement. Fundamentally, there is a PaymentQueue where all transactions will end up. You will have then to listen to this queue and act accordingly. The flow should look like this.

Flow chart

First, we will be making a separate class to handle StoreKit functions. Create a InAppPurchaseHandler.swift file to write In app purchase related code. It will be a singleton class.

import StoreKit

class MyStoreKitDelegate: NSObject {

    let monthlySubID = "MyApp.sub.allaccess.monthly"
    let yearlySubID = "MyApp.sub.allaccess.yearly"
    var products: [String: SKProduct] = [:]

    func fetchProducts() {
        let productIDs = Set([monthlySubID, yearlySubID])
        let request = SKProductsRequest(productIdentifiers: productIDs)
        request.delegate = self
        request.start()
    }

    func purchase(productID: String) {
        if let product = products[productID] {
            let payment = SKPayment(product: product)
            SKPaymentQueue.default().add(payment)
        }
    }

    func restorePurchases() {
        SKPaymentQueue.default().restoreCompletedTransactions()
    }
}

extension MyStoreKitDelegate: SKProductsRequestDelegate {

    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        response.invalidProductIdentifiers.forEach { product in
            print("Invalid: \(product)")
        }

        response.products.forEach { product in
            print("Valid: \(product)")
            products[product.productIdentifier] = product
        }
    }

    func request(_ request: SKRequest, didFailWithError error: Error) {
        print("Error for request: \(error.localizedDescription)")
    }

}
Enter fullscreen mode Exit fullscreen mode

Let’s go over it. Some key concepts are:

SKPaymentQueue: Queue where all transactions are being held for further processing

SKProduct: Product declared in Itunes Connect with all the available information

SKPayment: Intent of purchase of a product

SKPaymentTransaction: Event regarding a SKProduct

SKProductRequest: Request to fetch information about SKProducts giving the products ID

SKProductResponse: Response containing the desired products. It consists of two lists: products and invalid product identifiers. The first one will contain the successfully fetched SKProducts, the second one all the identifiers that fail to correlate to a SKProduct. If you are getting invalid identifiers, here is a troubleshooting list that could help you out.

Now, firstly, with the following lines, you can easily make a SKProductRequest.

let request = SKProductsRequest(productIdentifiers: productIDs)
request.delegate = self
request.start()
Enter fullscreen mode Exit fullscreen mode

Make sure you implement SKProductsRequestDelegate since the following method is the callback.

func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse
Enter fullscreen mode Exit fullscreen mode

There you will get all the SKProducts available for your app. To make a purchase go ahead and create a SKPayment.

let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
Enter fullscreen mode Exit fullscreen mode

> Make sure you always fetch the products even before allowing the user to make a purchase. These SKProducts will have the price localized inside them so you should display it. This will make sure you always have the data in your app up to date, as having the wrong price in the app can cause Apple to remove it from the App Store.

External event section

You may also notice in the flow an External event section. Transactions can happen outside your App. Say your user changes their subscription type in the system settings, or your deferred transaction was approved by the user’s parents, you won’t be able to tell unless you are expecting them. For that reason, always, in the AppDelegate, at the start of your app, subscribe the app to the PaymentQueue. This way you will make sure you won’t miss any event.

class AppDelegate: UIResponder, UIApplicationDelegate, SKPaymentTransactionObserver {

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        SKPaymentQueue.default().add(self)
        //...
        return true
    }

    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction in transactions {
            switch transaction.transactionState {
            case .failed:
                queue.finishTransaction(transaction)
                print("Transaction Failed \(transaction)")
            case .purchased, .restored:
                queue.finishTransaction(transaction)
                print("Transaction purchased or restored: \(transaction)")
            case .deferred, .purchasing:
                print("Transaction in progress: \(transaction)")
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Validating Receipts with the App Store

Paid software has always presented a problem where some users try to use the software without buying it or fraudulently access in-app purchases. Receipts provide a tool to confirm those purchases. They accomplish this by providing a record of sales. The App Store generates a receipt in the app bundle any time a user purchases your app, makes an in-app purchase, or updates the app.

Fetch the Receipt Data

To retrieve the receipt data from the app on the device, use the appStoreReceiptURL method of NSBundle to locate the app’s receipt, and encode the data in Base64. Send this Base64-encoded data to your server.

// Get the receipt if it's available
if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
    FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {

    do {
        let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
        print(receiptData)

        let receiptString = receiptData.base64EncodedString(options: [])

        // Read receiptData
    }
    catch { print("Couldn't read receipt data with error: " + error.localizedDescription) }
}
Enter fullscreen mode Exit fullscreen mode

Validate Receipt

There is two verifyReceiptURL for gathering the information from iTunes for both sandbox and live. The verifyReceiptURL API having 2 params. One is receipt-data which contains local receipt stored data and the second one is the password where you have to pass Shared Secret.

Sandbox: https://sandbox.itunes.apple.com/verifyReceipt
Live: https://buy.itunes.apple.com/verifyReceipt

Handling Refund Notifications

App Store Server sends near real-time notifications when customers receive refunds for in-app purchases. If you offer content across multiple platforms, for example, gems or coins for games, and you update player account balances on your server, receiving refund notifications is important. Respond to refund notifications by interpreting and handling the refund information, and informing customers in the app of any actions you take as a result of the refund.

When the App Store processes a refund, the App Store Server sends a REFUND notification to your server, at the URL you configured. Your server must respond to the post with a 200 response code.

The REFUND notification applies to consumable, non-consumable, and non-renewing subscriptions only.

Testing

Firstly, sign in to your iOS device using the created Sandbox user account, then run your application on the real device and initiate the transaction. Don’t worry about the price that is being displayed in the Alert window just go further. Nothing will be charged as you are a sandbox user for the application.

In App Purchases can not be tested on iOS simulator. So, Please use real device.

Top comments (0)