In-App Purchase Tutorial: Getting Started
Learn how to grow app revenue in this in-app purchase tutorial by allowing users to purchase or unlock content or features. By Pietro Rea.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
In-App Purchase Tutorial: Getting Started
30 mins
- Getting Started
- Creating an App ID
- Checking Your Agreements
- Creating an App in iTunes Connect
- Creating In-App Purchase Products
- Creating a Sandbox User
- Project Configuration
- Listing In-App Purchases
- Purchased Items
- Making Purchases (Show Me The Money!)
- Making a Sandbox Purchase
- Restoring Purchases
- Payment Permissions
- Where To Go From Here?
Purchased Items
You want to be able to determine which items are already purchased. To do this, you’ll use the purchasedProductIdentifiers
property added earlier. If a product identifier is contained in this set, the user has purchased the item. The method for checking this is straightforward.
In IAPHelper.swift, replace the return
statement in isProductPurchased(_:)
with the following:
return purchasedProductIdentifiers.contains(productIdentifier)
Saving purchase status locally alleviates the need to request such data to Apple’s servers every time the app starts. purchasedProductIdentifiers
are saved using UserDefaults
.
Still in IAPHelper.swift, replace init(productIds:)
with the following:
public init(productIds: Set<ProductIdentifier>) {
productIdentifiers = productIds
for productIdentifier in productIds {
let purchased = UserDefaults.standard.bool(forKey: productIdentifier)
if purchased {
purchasedProductIdentifiers.insert(productIdentifier)
print("Previously purchased: \(productIdentifier)")
} else {
print("Not purchased: \(productIdentifier)")
}
}
super.init()
}
For each product identifier, you check whether the value is stored in UserDefaults
. If it is, then the identifier is inserted into the purchasedProductIdentifiers
set. Later, you’ll add an identifier to the set following a purchase.
UserDefaults
plist, and modify it to ‘unlock’ purchases. If this sort of thing concerns you, then it’s worth checking out Apple’s documentation on Validating App Store Receipts — this allows you to verify that a user has made a particular purchase.Making Purchases (Show Me The Money!)
Knowing what a user has purchased is great, but you still need to be able to make the purchases in the first place! Implementing purchase capability is the logical next step.
Still in IAPHelper.swift, replace buyProduct(_:)
with the following:
public func buyProduct(_ product: SKProduct) {
print("Buying \(product.productIdentifier)...")
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
}
This creates a payment object using an SKProduct
(retrieved from the Apple server) to add to a payment queue. The code utilizes a singleton SKPaymentQueue
object called default()
. Boom! Money in the bank. Or is it? How do you know if the payment went through?
Payment verification is achieved by having the IAPHelper
observe transactions happening on the SKPaymentQueue
. Before setting up IAPHelper
as an SKPaymentQueue
transactions observer, the class must conform to the SKPaymentTransactionObserver
protocol.
Go to the very bottom (after the last curly brace) of IAPHelper.swift and add the following extension:
// MARK: - SKPaymentTransactionObserver
extension IAPHelper: SKPaymentTransactionObserver {
public func paymentQueue(_ queue: SKPaymentQueue,
updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchased:
complete(transaction: transaction)
break
case .failed:
fail(transaction: transaction)
break
case .restored:
restore(transaction: transaction)
break
case .deferred:
break
case .purchasing:
break
}
}
}
private func complete(transaction: SKPaymentTransaction) {
print("complete...")
deliverPurchaseNotificationFor(identifier: transaction.payment.productIdentifier)
SKPaymentQueue.default().finishTransaction(transaction)
}
private func restore(transaction: SKPaymentTransaction) {
guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }
print("restore... \(productIdentifier)")
deliverPurchaseNotificationFor(identifier: productIdentifier)
SKPaymentQueue.default().finishTransaction(transaction)
}
private func fail(transaction: SKPaymentTransaction) {
print("fail...")
if let transactionError = transaction.error as NSError?,
let localizedDescription = transaction.error?.localizedDescription,
transactionError.code != SKError.paymentCancelled.rawValue {
print("Transaction Error: \(localizedDescription)")
}
SKPaymentQueue.default().finishTransaction(transaction)
}
private func deliverPurchaseNotificationFor(identifier: String?) {
guard let identifier = identifier else { return }
purchasedProductIdentifiers.insert(identifier)
UserDefaults.standard.set(true, forKey: identifier)
NotificationCenter.default.post(name: .IAPHelperPurchaseNotification, object: identifier)
}
}
That’s a lot of code! A detailed review is in order. Fortunately, each method is quite short.
paymentQueue(_:updatedTransactions:)
is the only method actually required by the protocol. It gets called when one or more transaction states change. This method evaluates the state of each transaction in an array of updated transactions and calls the relevant helper method: complete(transaction:)
, restore(transaction:)
or fail(transaction:)
.
If the transaction was completed or restored, it adds to the set of purchases and saves the identifier in UserDefaults
. It also posts a notification with that transaction so that any interested object in the app can listen for it to do things like update the user interface. Finally, in both the case of success or failure, it marks the transaction as finished.
All that’s left is to hook up IAPHelper
as a payment transaction observer. Still in IAPHelper.swift, go back to init(productIds:)
and add the following line right after super.init()
.
SKPaymentQueue.default().add(self)
Making a Sandbox Purchase
Build and run the app — but to test out purchases, you’ll have to run it on a device. The sandbox tester created earlier can be used to perform the purchase without getting charged. If only I could have a sandbox tester to do my grocery shopping :] Here’s how to use the tester account:
Go to your iPhone and make sure you’re logged out of your normal App Store account. To do this, go to the Settings app and tap iTunes & App Store.
Tap your iCloud account name and then tap Sign Out. At this point don’t actually sign in with the sandbox user. You will be prompted to do this once you attempt to buy the IAP back in the sample app.
Connect your device, build and run! You’ll see your product listed in the app. To begin the purchase process, tap the Buy button.
An alert will appear prompting you to log in. Tap Use Existing Apple ID, and enter the login details for the sandbox tester account that you created earlier.
Confirm the purchase by tapping Buy. The alert view shows that the purchase is being made in the sandbox as a reminder that you won’t be charged for it.
Finally, an alert view will appear confirming the purchase was successful. Once the purchase process has been completed, a checkmark appears next to the purchased item. Tap on the purchased item to enjoy your new RazeFace.
Finally you get to see this “Swift Shopping” RazeFace that you’ve been hearing so much about!
Restoring Purchases
If the user deletes and re-installs the app or installs it on another device, then they need the ability to access previously purchased items. In fact, Apple may reject an app if it cannot restore non-consumable purchases.
As a purchase transaction observer, IAPHelper
is already being notified when purchases have been restored. The next step is to react to this notification by restoring the purchases.
Open IAPHelper.swift and scroll to the bottom of the file. In the StoreKit API extension, replace restorePurchases()
with the following:
public func restorePurchases() {
SKPaymentQueue.default().restoreCompletedTransactions()
}
That was almost too easy! You’ve already set the transaction observer and implemented the method to handle restoring transactions in the previous step.
To test this out, after you’ve made a purchase in the previous step, delete the app from your device. Build and run again, then tap Restore on the top right. You should see a checkmark appear next to the previously purchased product.