In-app purchase (IAP) is a huge way to earn money from your iPhone, iPad, iPod touch or Mac app. You can identify if some app provides IAP by going to the app page, and observe if it has “Offers In-App Purchases” or “In-App Purchases” near the Price, Buy, or Get button. IAP is generally used to unlock some extra content in your app that you desire to monetize from. Like it or not, Apple charges 30% of each successful transaction that your app will build.
Examples of IAP:
Open the Medium iOS app and you can observe a “Become a member” button under Settings, which provides a monthly subscription with lots of premium content;
Get coins or other virtual currency in a game, so you can progress quicker.
Types of IAP:
Consumable: the user wants to get these items all time he wants them. Examples of consumable purchases are trading game currency, hints, health etc.
Non-Consumable: once you get 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: improvement an app to pro version, removing ads, city guide maps etc.
Non-renewing subscriptions: with the app content for a fixed period of time, and you can buy it again after it ends. For example, a sports season.
Auto-renewing subscriptions: you can promise to the content or the services that the app is providing for a definite period. It will repeatedly renovate when the period has passed. Examples, newspaper subscriptions, Netflix, games, etc.
With this blog, you will study everything that you want to know about In-App Purchases. I will seek to be as much detailed as possible, and will break down this tutorial to multiple steps for better considerate:
- iTunes Connect Setup
- The Code
- Using the Code
- Testing
iTunes Connect Setup
Enter your bank account details
The most significant part to obtain the IAP work is to enter your bank information. That can be done by obtainable to the Agreements, Tax, and Banking section in iTunes Connect. If you don’t have this setup, you can’t utilize the IAP services. Must stare something like this below…
Build a Sandbox User
Then we require building a Sandbox User. To build this kind of user navigate to Users and Roles and select the Sandbox Testers section. Consider to utilize an email that isn’t connected with any Apple ID. You will require the Sandbox User to build test payments; or else, you can’t test them.
Build an iTunes App
Navigate to the My Apps section and build an app. Or, utilize an accessible app if you already have one. To build an app you will have to build an App ID from your Developer account.
Build the IAP products
Tap on your iTunes app and go to the Features section. There you can build a new IAP product. Tap on the + icon and select one of the 4 types that I have described you above. Then enter the required metadata associated to that IAP. Pay attention to the Product ID, as you will require that identifier in your app to call the desired IAP. Here is how it should look like.
The following are some examples of IAP’s that I have created. The warnings are totally normal.
The iTunes Connect setup is completed. If you are understand, then lets move to the next steps where I will explain you the code
The Code
Maintain the code in a separate class. I have named mine IAPHandler, but feel free to modify the name if you don’t like it. In this class, we will store all related to In-App Purchases. For a better understanding, I will add the whole GIST file and will clarify each function in the file…
Variables
The first thing that you want to do is to build variables from your IAP product ID’s (in my case CONSUMABLE_PURCHASE_PRODUCT_ID and NON_CONSUMABLE_PURCHASE_PRODUCT_ID). Also, we will build variables for handling the IAP request and an array that will store all the available IAP products.
Class functions
canMakePurchases() — proceeds a boolean value whether the device is able to create purchases or not.
purchaseMyProduct(index: Int) —utilize this function for starting a purchase. This function will increase the payment dialog. Send index to obtain the correct IAP product from the iapProducts array.
restorePurchase() — function for restoring the IAP. Utilized if the user modifies a device, and he already holds a non-consumable IAP in your app.
fetchAvailableProducts() — build a group of product ID’s that you want to use, by including each of them into an NSSet object. Consider to set the delegate method, so you can obtain the SKProduct results back.
Delegate Methods
productsRequest (_ request:SKProductsRequest, didReceive response:SKProductsResponse) — returns every available In-App Purchases and populates the iapProducts array. Triggered after calling the fetchAvailableProducts() function.
paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) — knob a situation where a user successfully restores an IAP.
paymentQueue(_ queue: SKPaymentQueue, modernized Transactions transactions: [SKPaymentTransaction]) — this delegate method is generated after calling the purchaseMyProduct(index: Int) function. In this callback, you will find all associated to the IAP transaction ( like if an item has been purchased or it failed).
Callback handler
As a bonus, I have included the callback handling enum. Generate a closure purchaseStatusBlock(), which returns different IAP transaction statuses for further clearer code. To make it even better, I have formed 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 }}} } }
Using the Code
Navigate to your UIViewController and then in your in viewDidLoad() function, fetch the products and also include the closure where you will find the response of the transaction that the user has made.
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) } }
Then build an action where you will start the transaction window. The below example will obtain the first product from the iapProducts array.
@IBAction func consumable(btn: UIButton){ IAPHandler.shared.purchaseMyProduct(index: 0) }
Testing
Sign In with your Sandbox User on your iOS device,go to your app and tap the action to start the transaction. No need to worry about the price on the transaction window. Because you are using a Sandbox User, nothing will be charged from your account.
Leave a Reply