Introduction
In-app purchase is a great feature for all those developers who want to get more revenue and offer extra content and features through their applications. For example, for games you can buy gems or coins, and for photography apps you may unlock new effects or tools. And you can do all this using a credit card or other payment method, without exiting the app.
In this tutorial I'll cover all the necessary steps to create a Consumable and Non-Consumable IAP product on iTunes Connect, and I'll show you the code you'll need to purchase both items. I've made a sample Xcode project with a label and two buttons, so download it and follow along with this tutorial to understand how it works.
Create a Sandbox Tester in iTunes Connect
I assume you've already created an iOS app in the My Apps section on iTunes Connect. The first thing you should do is create a Sandbox Tester to test IAP on your real device (no Simulator—it doesn't support In-App Purchases).
Enter Users and Roles, go to the Sandbox Tester tab, and click the (+) sign next to Tester.
Fill out the form to add a new sandbox tester. Once you've saved your info, go back to the My App section and click on the icon of your app to enter its details and create IAP products.
Create IAP Products in iTunes Connect
Consumable Products
Click the Features tab and then the (+) sign next to In-App Purchases. You can create one product at a time, so let's start with a Consumable one.
A Consumable IAP, as its name suggests, is a product that you can buy multiple times. We'll use it to collect additional "coins" in our demo app.
Click Create to initialize your IAP item. On the next screen, you can set up all the info about your product:
- Reference Name: this name will be used on iTunes Connect and in Sales and Trends reports. It won't be displayed on the App Store and you can type any name you want, but it can't be longer than 64 characters.
- Product ID: A unique alphanumeric identifier that will be fetched by the app in order to recognize your product. Usually developers use a web-reverse syntax for product ids. In this example we chose com.iaptutorial.coins. Later on we'll paste this ID as a string into our code.
- Price: Choose a price tier from the dropdown menu. Remember that in order to sell your in-app purchase product on the App Store, you must have applied for a Paid Application Agreement in the Agreements, Tax & Banking section.
- Localizations: For the sake of this tutorial we've chosen only English, but you can add more languages by clicking on the (+) button. Then type a Display Name and a Description. Both of them will be visible on the App Store.
- Screenshot: Upload a screenshot for review. It will not be displayed in the App Store and it must have a valid size for your app platform, so if your app is Universal, you may upload an iPad screenshot.
- Review Notes: Any additional information about your IAP which may be helpful for the reviewer.
Once you're done, click Save and you'll get this alert:
Your first In-App Purchase must be submitted with a new app version. Select it from the app’s In-App Purchases section and click Submit.
Non-Consumable Products
Now click the In-App Purchases button on the list on the left, right above the Game Center button, and add a new IAP product. This time, select the Non-Consumable option:
Click Create and repeat the steps we mentioned above. Since this will be a Non-Consumable product, users will be able to purchase it only once, and Apple requires the ability to restore such purchases. That happens in case you uninstall the app and reinstall it again, or download it from another device with your same Apple ID and need to get your purchases back without paying for them twice. So later we'll add a Restore Purchase function in our code.
The Product ID we created now is com.iaptutorial.premium, with a price tier of USD $2.99. We've called it Unlock Premium Version.
Once you're done filling all the fields, save your product and go back to the In-App Purchases page. Now you should have a list of your two products, with their Name, Type, ID and Status set as Ready to Submit.
Go back to your app's page by clicking on the App Store and Prepare for Submission buttons. Scroll down to the In-App Purchases section, right below General App Information, and click to the (+) button to add your IAP products.
Select all of them and click Done.
Finally, click Save in the top-right corner of the screen and you'll be done with configuring In-App Purchase products on iTunes Connect.
Log in to Sandbox Tester on an iOS device
Before getting to the code, there's one more thing left to do. Go to Settings > iTunes & App Store on your iOS device. If you're already logged in with your original Apple ID, tap on it and choose Sign Out. Then simply sign in with the credentials for the sandbox tester you created. After signing in, you may get an alert like this:
Just ignore its message and tap Cancel. Your device will ask you your sandbox login credentials again while trying to make a purchase and will recognize your test account so you won't be charged a penny on your credit card for any purchase you make.
Exit Settings, plug your device into your Mac via the USB cable, and let's finally start coding!
The Code
If you've downloaded our demo project, you'll see that all the necessary code for In-App Purchase has been written, so if you run it you'll get something like this:
If you want to test the app, you should change the Bundle Identifier to your own id. Otherwise, Xcode will not allow you to run the app on a real device and the app will not recognize the two IAP products you've created.
Enter ViewController.swift and check the code. First of all we've added an import statement for StoreKit
and the delegates we need in order to track payment transactions and product requests.
import StoreKit class ViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver {
Then we've declared a few views which will be useful.
/* Views */ @IBOutlet weak var coinsLabel: UILabel! @IBOutlet weak var premiumLabel: UILabel! @IBOutlet weak var consumableLabel: UILabel! @IBOutlet weak var nonConsumableLabel: UILabel!
coinsLabel
and premiumLabel
will be used to show the results of purchases for both products. consumableLabel
and nonConsumableLabel
will show the description and price of each IAP product, the ones we've previously created in iTunes Connect.
Now it's time to add some variables:
/* Variables */ let COINS_PRODUCT_ID = "com.iaptutorial.coins" let PREMIUM_PRODUCT_ID = "com.iaptutorial.premium" var productID = "" var productsRequest = SKProductsRequest() var iapProducts = [SKProduct]() var nonConsumablePurchaseMade = UserDefaults.standard.bool(forKey: "nonConsumablePurchaseMade") var coins = UserDefaults.standard.integer(forKey: "coins")
The first two lines are to recall our product IDs. It's important that those strings exactly match the ones registered in iTunes Connect's In-App Purchase section.
-
productID
is a string we'll be using later to detect what product we will choose to buy. -
productsRequest
is an instance ofSKProductsRequest
, needed to search for IAP products from your app on iTC. -
iapProducts
is a simple array ofSKProducts
. Please note that SK prefix means StoreKit, the iOS framework we'll be using to handle purchases.
The last two lines load two variables of type Boolean
and Integer
needed to track purchases of coins and the premium version, respectively consumable and non-consumable products.
The following code in viewDidLoad()
performs a few things as soon as the app starts:
// Check your In-App Purchases print("NON CONSUMABLE PURCHASE MADE: \(nonConsumablePurchaseMade)") print("COINS: \(coins)") // Set text coinsLabel.text = "COINS: \(coins)" if nonConsumablePurchaseMade { premiumLabel.text = "Premium version PURCHASED!" } else { premiumLabel.text = "Premium version LOCKED!"} // Fetch IAP Products available fetchAvailableProducts()
First we just log each purchase to the Xcode console. Then we display the total amount of coins that we bought with the coinsLabel
. Since we're running the demo app for the first time, it will show COINS: 0.
The if
statement sets the premiumLabel
's text according to whether the non-consumable product was purchased. To start off, it will show Premium version LOCKED! since we haven't made the premium purchase yet.
The last line of code calls a method we'll see later, which simply fetches the products we've previously stored in iTC.
Now let's see what the two purchase buttons we've set in our demo app do:
// MARK: - BUY 10 COINS BUTTON @IBAction func buy10coinsButt(_ sender: Any) { purchaseMyProduct(product: iapProducts[0]) } // MARK: - UNLOCK PREMIUM BUTTON @IBAction func unlockPremiumButt(_ sender: Any) { purchaseMyProduct(product: iapProducts[1]) }
Both methods will call a function that will check if the device can make purchases, and if it can, the app will call the StoreKit delegate methods to process the purchase.
As mentioned before, we need a third button to restore our non-consumable purchase. Here is its code:
// MARK: - RESTORE NON-CONSUMABLE PURCHASE BUTTON @IBAction func restorePurchaseButt(_ sender: Any) { SKPaymentQueue.default().add(self) SKPaymentQueue.default().restoreCompletedTransactions() } func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { nonConsumablePurchaseMade = true UserDefaults.standard.set(nonConsumablePurchaseMade, forKey: "nonConsumablePurchaseMade") UIAlertView(title: "IAP Tutorial", message: "You've successfully restored your purchase!", delegate: nil, cancelButtonTitle: "OK").show() }
The IBAction
function is attached to the Restore Purchase button in the Storyboard and starts connecting to Apple's In-App Purchase system to restore the purchase if that has been already made.
paymentQueueRestoreCompletedTransactionsFinished()
is the delegate method from StoreKit framework that will save our nonConsumablePurchaseMade
variable to true after the purchase has been successfully restored.
We're done with buttons, so let's see what the fetchAvailableProducts()
function does:
// MARK: - FETCH AVAILABLE IAP PRODUCTS func fetchAvailableProducts() { // Put here your IAP Products ID's let productIdentifiers = NSSet(objects: COINS_PRODUCT_ID, PREMIUM_PRODUCT_ID ) productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers as! Set<String>) productsRequest.delegate = self productsRequest.start() }
We first create an instance of NSSet
, which is basically an array of strings. We'll store the two product IDs we've previously declared there.
Then we start an SKProductsRequest
based on those identifiers, in order for the app to display the info about the IAP products (description and price), which will be processed by this delegate method:
// MARK: - REQUEST IAP PRODUCTS func productsRequest (_ request:SKProductsRequest, didReceive response:SKProductsResponse) { if (response.products.count > 0) { iapProducts = response.products // 1st IAP Product (Consumable) ------------------------------------ let firstProduct = response.products[0] as SKProduct // Get its price from iTunes Connect let numberFormatter = NumberFormatter() numberFormatter.formatterBehavior = .behavior10_4 numberFormatter.numberStyle = .currency numberFormatter.locale = firstProduct.priceLocale let price1Str = numberFormatter.string(from: firstProduct.price) // Show its description consumableLabel.text = firstProduct.localizedDescription + "\nfor just \(price1Str!)" // ------------------------------------------------ // 2nd IAP Product (Non-Consumable) ------------------------------ let secondProd = response.products[1] as SKProduct // Get its price from iTunes Connect numberFormatter.locale = secondProd.priceLocale let price2Str = numberFormatter.string(from: secondProd.price) // Show its description nonConsumableLabel.text = secondProd.localizedDescription + "\nfor just \(price2Str!)" // ------------------------------------ } }
In the function above we first have to check if there are any products registered in iTunes Connect and set our iapProducts
array accordingly. Then we can initialize the two SKProducts and print their description and price on the labels.
Before getting to the core of the In-App Purchase code, we need a couple more functions:
// MARK: - MAKE PURCHASE OF A PRODUCT 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) print("PRODUCT TO PURCHASE: \(product.productIdentifier)") productID = product.productIdentifier // IAP Purchases dsabled on the Device } else { UIAlertView(title: "IAP Tutorial", message: "Purchases are disabled in your device!", delegate: nil, cancelButtonTitle: "OK").show() } }
The first one checks if our device is able to make purchases. The second function is the one that we call from the two buttons. It starts the payment queue and changes our productID
variable into the selected productIdentifier
.
Now we've finally arrived at the last delegate method, the one that handles payment results:
// 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: SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction) // The Consumable product (10 coins) has been purchased -> gain 10 extra coins! if productID == COINS_PRODUCT_ID { // Add 10 coins and save their total amount coins += 10 UserDefaults.standard.set(coins, forKey: "coins") coinsLabel.text = "COINS: \(coins)" UIAlertView(title: "IAP Tutorial", message: "You've successfully bought 10 extra coins!", delegate: nil, cancelButtonTitle: "OK").show() // The Non-Consumable product (Premium) has been purchased! } else if productID == PREMIUM_PRODUCT_ID { // Save your purchase locally (needed only for Non-Consumable IAP) nonConsumablePurchaseMade = true UserDefaults.standard.set(nonConsumablePurchaseMade, forKey: "nonConsumablePurchaseMade") premiumLabel.text = "Premium version PURCHASED!" UIAlertView(title: "IAP Tutorial", message: "You've successfully unlocked the Premium version!", delegate: nil, cancelButtonTitle: "OK").show() } break case .failed: SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction) break case .restored: SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction) break default: break }}} }
This function has a switch
statement that checks every state of the payment. The first case
gets called if the purchase has been successfully made and completes its transaction.
Inside this block we have to check what product ID we've selected and perform the necessary actions to update our app—so if we the user bought 10 extra coins, we'll add 10 to our coins
variable, save its value with UserDefaults
, display the new amount of coins we gained, and fire an alert about it.
Please note that you can make this purchase multiple times with no limits since it's a consumable IAP, and there's no need for a restore purchase function.
Similarly, if we bought the non-consumable premium product, the app sets our nonConsumablePurchaseMade
variable to true
, saves it, changes the text of the premiumLabel
, and fires an alert to notify you that the purchase has been successful.
The other two cases
handle the payment results for failure and restoring. The app will fire custom alerts on its own if your transaction fails for some reason or if you've restored a non-consumable purchase.
That's it! Now just make sure you're logged in with your Sandbox Tester credentials and run the app to test it. The first time, you'll get an alert like this:
Choose Use Existing Apple ID and enter your Sandbox Tester's username and password again to sign in. This happens because the app can recognize only a real user from the iTunes & App Store settings, not a Sandbox one.
Once you've logged in, you'll be able to perform purchases of both products.
CodeCanyon Templates
If you work with iOS and want to get deeper into Swift language and apps development, check some of my iOS app templates on CodeCanyon.
There are hundreds of other iOS app templates on the Envato Market as well, ready to be reskinned and sure to speed up your workflow. Go check them out! You might just save hours of work on your next app.
Conclusion
In this tutorial, we've covered all the steps needed to create In-App Purchase products on iTunes Connect and how to write the code to enable them in your app. I hope you're able to put this knowledge to use in your next iOS app!
Thanks for reading, and I'll see you next time! Please check out our other courses and tutorials about iOS app development with Swift.
Comments