In this tutorial we are going to review how to setup an In-App Purchase (IAP) for your app using Swift. This will show you step by step how to setup an IAP in iTunes Connect for your app, then how to implement it in your app and how you can unlock features or consumables for the user.
Enable IAP in the Developer Portal
The first step is to enable In-App Purchases when you create the App ID for your app. Make sure this is correct by logging into the developer center, then navigating to Certificates, Identifiers & Profiles, App Ids and checking that In-App Purchases are Enabled. They are enabled by default when you create a new app.
Define the IAP in iTunes Connect
The next step is to list the In-App Purchases you would like to include in your app. To do this you already need to have your bundle ID and your app setup in iTunes Connect. To begin listing your IAP’s, go into your app within iTunes Connect and select “In-App Purchases”.
Next if you don’t already have any listed, click “Create New”.
Then you’ll need to select the type of In-App Purchase you need from the 5 types. There are two major types, Consumables and Subscriptions. Both are divided into two categories: for Consumables, we have Non-Consumable and Consumable. For Subscriptions, we have Renewable and Non-Renewable subscriptions. The choice of which one to implement will depend on your application and what you need to give users access to with their purchase.
For each IAP that you define, you need to enter a Reference Name (only displayed to you to remember which IAP is which), Product ID (the string thats used in the code to reference this product) and Price Tier (How much you are going to charge). Every item must also have a product identifier that is associated with your application and uniquely identifies an item sold. Your application uses this product id to fetch localized item descriptions and pricing information from the App Store and to request payments.
Enter in the localized item name and description per language that your app supports. This will be displayed to the user before they agree to pay for your product.
Make sure you upload a screenshot for each IAP to show how it works, and where it is purchased from. You can also leave a review note so that you can explain the overall idea and flow. This will help the review team understand what the IAP is for and may save you a lot of back and forth and get it approved faster. The screenshots and the review note won’t be seen by the end users, they only support the app review team when they are reviewing your app.
I’ve created an In-App Purchase for each one of the 5 types for testing.
Implementing StoreKit
The interactions between the user, your app, and the App Store during the In-App Purchase process take place in three stages, as shown below. First, the user navigates to your app’s store and your app displays its products. Second, the user selects a product to buy and the app requests payment from the App Store. Third, the App Store processes the payment and your app delivers the purchased product.
First let’s start off by declaring the delegates and properties needed for the class. The SKProductsRequestDelegate is used for the callback method needed when retrieving the product information from iTunes Connect.
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, SKProductsRequestDelegate { var tableView = UITableView() let productIdentifiers = Set(["com.brianjcoleman.testiap1", "com.brianjcoleman.testiap2", "com.brianjcoleman.testiap3", "com.brianjcoleman.testiap4", "com.brianjcoleman.testiap5"]) var product: SKProduct? var productsArray = Array()
We are going to use a UITableView to display our products. There is a set defined with the unique product identifiers that we setup in iTunes Connect for each one of our products. Finally the product is of type SKProduct which is an object that contains the title, description and price for a product. Then the productArray will be used to store all of the products we receive before we present it to the user.
Stage 1: Retrieving Product Information
To make sure your users see only products that are actually available for purchase, query the App Store before displaying your app’s store UI. This is valuable so you can change the title, description and prices for your products anytime without having to update your app.
In the viewDidLoad(), call the requestProductData() method to retrieve the product information for each one of the product identifiers defined above. It’s best to do a check to determine if the users device has In-App Purchases enabled. If they can make payments then the SKProductsRequest is made to iTunes Connect.
func requestProductData() { if SKPaymentQueue.canMakePayments() { let request = SKProductsRequest(productIdentifiers: self.productIdentifiers as Set) request.delegate = self request.start() } else { var alert = UIAlertController(title: "In-App Purchases Not Enabled", message: "Please enable In App Purchase in Settings", preferredStyle: UIAlertControllerStyle.Alert) alert.addAction(UIAlertAction(title: "Settings", style: UIAlertActionStyle.Default, handler: { alertAction in alert.dismissViewControllerAnimated(true, completion: nil) let url: NSURL? = NSURL(string: UIApplicationOpenSettingsURLString) if url != nil { UIApplication.sharedApplication().openURL(url!) } })) alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: { alertAction in alert.dismissViewControllerAnimated(true, completion: nil) })) self.presentViewController(alert, animated: true, completion: nil) } } func productsRequest(request: SKProductsRequest!, didReceiveResponse response: SKProductsResponse!) { var products = response.products if (products.count != 0) { for var i = 0; i < products.count; i++ { self.product = products[i] as? SKProduct self.productsArray.append(product!) } self.tableView.reloadData() } else { println("No products found") } products = response.invalidProductIdentifiers for product in products { println("Product not found: \(product)") } }
Since the delegate SKProductsRequestDelegate is used, after the SKProductsRequest is made the callback method productsRequest is performed. In this method we store all of the product objects into the productsArray so we can populate our UITableView with the products so the user can select one to purchase.
Run your app to ensure the In-App Purchases are coming back from iTunes Connect. Here's what mine look like after I've added them to a UITableView.
While in development it's good to check to see if all of your products are coming through correctly by adding the error checking for invalid product identifiers. If you are not getting any products back from Apple when you are trying to retrieve them look to this checklist:
http://www.gamedonia.com/game-development/solve-invalid-product-ids.
Stage 2: Requesting Payment
So now you've presented the list of products to the user, what's next? Well if you're providing good value or an amazing feature the user wants then they will want to buy one of your products.
func buyProduct(sender: UIButton) { let payment = SKPayment(product: productsArray[sender.tag]) SKPaymentQueue.defaultQueue().addPayment(payment) }
This method uses the button.tag property to get the correct product from the productsArray so the same method can be used for all products. You could also setup a separate function per product.
The user will then be prompted with the standard Apple In-App Purchase dialog asking them to confirm their purchase.
Note: While you can test up to here using the Simulator you will need to test on the device to actually make a successful purchase. To test all In-App Purchases before they are submitted to Apple for review you'll be working in the Sandbox mode. Start by setting up a Sandbox (test account) in from iTunes Connect account. Users & Roles > Sandbox Testers.
When testing on a device be sure to log out of your production iTunes account in the App Store before trying to purchase from a Sandbox account so you'll be prompted to login using your Sandbox Testing account. On the Simulator it will ask you to login (as shown below).
Step 3 - Delivering Products
Once the user has confirmed they would like to purchase your product, the paymentQueue delegate method is invoked, but in order to be able to use the updatedTransactions method we need to declare it's delegate "SKPaymentTransactionObserver" in our class first.
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, SKProductsRequestDelegate, SKPaymentTransactionObserver
Also add "SKPaymentQueue.defaultQueue().addTransactionObserver(self)" to your viewDidLoad to register your class with the delegate and add the transaction observer. Now we can add the following function to capture the approved or failed transaction from Apple.
func paymentQueue(queue: SKPaymentQueue!, updatedTransactions transactions: [AnyObject]!) { for transaction in transactions as! [SKPaymentTransaction] { switch transaction.transactionState { case SKPaymentTransactionState.Purchased: println("Transaction Approved") println("Product Identifier: \(transaction.payment.productIdentifier)") self.deliverProduct(transaction) SKPaymentQueue.defaultQueue().finishTransaction(transaction) case SKPaymentTransactionState.Failed: println("Transaction Failed") SKPaymentQueue.defaultQueue().finishTransaction(transaction) default: break } } } func deliverProduct(transaction:SKPaymentTransaction) { if transaction.payment.productIdentifier == "com.brianjcoleman.testiap1" { println("Consumable Product Purchased") // Unlock Feature } else if transaction.payment.productIdentifier == "com.brianjcoleman.testiap2" { println("Non-Consumable Product Purchased") // Unlock Feature } else if transaction.payment.productIdentifier == "com.brianjcoleman.testiap3" { println("Auto-Renewable Subscription Product Purchased") // Unlock Feature } else if transaction.payment.productIdentifier == "com.brianjcoleman.testiap4" { println("Free Subscription Product Purchased") // Unlock Feature } else if transaction.payment.productIdentifier == "com.brianjcoleman.testiap5" { println("Non-Renewing Subscription Product Purchased") // Unlock Feature } }
If the purchase is approved we need to make sure to deliver the functionality or product to the user and remove the transaction from the queue. In this example I am capturing the transaction and passing it to a deliverProduct method where I'll unlock the feature for the user.
Restore Purchases
If your app offers Non-Consumable or any Subscription based In-App Purchases Apple requires that you provide a Restore Purchases button so users who have already purchased your product can get access to it again if they use your app on another Apple device (i.e. another iPhone, iPad or Apple TV).
func restorePurchases(sender: UIButton) { SKPaymentQueue.defaultQueue().addTransactionObserver(self) SKPaymentQueue.defaultQueue().restoreCompletedTransactions() } func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue!) { println("Transactions Restored") var purchasedItemIDS = [] for transaction:SKPaymentTransaction in queue.transactions as! [SKPaymentTransaction] { if transaction.payment.productIdentifier == "com.brianjcoleman.testiap1" { println("Consumable Product Purchased") // Unlock Feature } else if transaction.payment.productIdentifier == "com.brianjcoleman.testiap2" { println("Non-Consumable Product Purchased") // Unlock Feature } else if transaction.payment.productIdentifier == "com.brianjcoleman.testiap3" { println("Auto-Renewable Subscription Product Purchased") // Unlock Feature } else if transaction.payment.productIdentifier == "com.brianjcoleman.testiap4" { println("Free Subscription Product Purchased") // Unlock Feature } else if transaction.payment.productIdentifier == "com.brianjcoleman.testiap5" { println("Non-Renewing Subscription Product Purchased") // Unlock Feature } } var alert = UIAlertView(title: "Thank You", message: "Your purchase(s) were restored.", delegate: nil, cancelButtonTitle: "OK") alert.show() }
The paymentQueueRestoreCompletedTransactionsFinished delegate method will loop through each In-App Purchase and check the product identifier of it. For each product identifier it find’s it will unlock the feature, just like the original deliverProduct(transaction:SKPaymentTransaction) method.
You can grab the full source code for this tutorial. Note: Created using XCode 6.3 (Swift 1.2).
If you would like to add receipt validation code to your app to prevent unauthorized purchases and keep a record of successful in-app purchases read Tutorial: Receipt Validation in Swift.