Using In-App Purchases

      10 Comments on Using In-App Purchases

When I was setting up In-App purchases I never dreamed that I'd be spending so much time setting things up on iTunes Connect. Implementing it in the app was actually quite simple.When I was setting up In-App purchases I never dreamed that I’d be spending so much time setting things up on iTunes Connect. Implementing it in the app was actually quite simple. In this article we’ll focus mainly on how to set everything up on your account. At the end we’ll quickly go over the code and implement a simple non-consumable purchase. The good thing is, once you have everything set up it will be very simple for you to add new In-App purchase items. Let’s dive in.

iTunes Connect

It goes without saying, you’ll need a developer account in order to use In-App purchases. You’ll also need your bank account details (BIC, sort code…) and, depending on the country you live in, your tax details.

Legal Stuff

When I was implementing In-App purchases I did everything else before I did all the paperwork. Then I wondered why things weren’t working. You’re better off doing the paperwork right away, and just getting it out-of-the-way.

You will need to get into the paid applications agreement with Apple. This means that you will need to be able to enter into legal agreements in your country. This shouldn’t be a problem anyway. When you login to your iTunes Connect, select ‘Agreements, Tax, and Banking’ from the drop-down menu:

Here you’ll see a list of available contracts. You’ll need to request a ‘Paid Applications’ contract. Simply select the ‘Request’ button as shown:

When you request the contract, you’ll need to agree to the terms and conditions. After that, you’ll see a new contract in process:

Contact, Bank and Tax info are all required and you’ll need to do all of them in order for you contract to take effect. Contact and Bank info are pretty straight forward and I didn’t have any problems with them. Tax info was a royal pain in the… This form will be different depending on where you live in the world. I live in Ireland so filling it out was quite straight forward (after a few hours of trying to figure it out, it turned out I didn’t have to fill most of the fields πŸ™‚ ). If you live in U.S. you might have a fun time filling it out, best of luck. I’m not going to show you any screenshots of these forms, due to the nature of personal info they hold.

When you’re finished with all the forms, you should have a new contract in effect for paid applications, something like this:

If you see your contract in master agreements, all is well. We can proceed to something less boring.

In-App Purchases

Assuming you already have an app set up on iTunes Connect, you’ll need to create some products in order to use In-App purchases. These are the digital goods that you actually sell. For example: option to remove ads from your app, buying a level for your game… So let’s create a product. Select an app you want to create products for, then select ‘Features’ -> ‘In-App Purchases’ like so:

From here you can create new products by clicking on that plus button. Do that and you’ll have to select the type of the In-App purchase you want:

Select the one that’s most appropriate for you. I’ll be talking about ‘Non-Consumable’ type in this article. A good example of a Non-Consumable would be removing ads from an app. After selecting ‘Create’ you’ll need to finish filling out the form to create your product. Write down your ‘Product ID’ because you’ll be using it to identify this product from your code. Make sure your product ID starts with your bundle ID. For example: com.mydomain.app.purchases.removeads. You can also set the price here.

All fields are mandatory except ‘App Store Promotion’. Once you have everything filled out, save your product. If you see ‘Ready To Submit’ you’re done with the setup:

Your In-App purchase items get reviewed when you submit your app and you need to select the In-App purchase items that you want to include with the release of your app. If you see ‘Missing Metadata’ instead, then you probably didn’t fill the ‘Review Information’ fields. You’ll need to upload the screenshot of your app where the user can access the ‘buy’ button from. And you’ll need to add a short description on how to use it. Remember, there’s a person on the other side, testing your app, so help them out.

Sandbox Testers

You’ll need to create some sandbox users so you can test your purchases. From the drop-down menu select ‘Users and Roles’:

You’ll see a tab ‘Sandbox Testers’, add a couple of test accounts here:

You can use the trick with labels like I did here. Use your real email and add a plus sign and some meaningful label before the @ symbol.

Some In-App purchases are one-off purchases. You can only buy the item once for that account. Like in our example. Once the user buys an item he can’t buy it again, he can restore it. So you might want to create a couple of test accounts here.

And with that, we’re done with iTunes Connect. Let’s set the project up.

Project Setup

Preparing your project for In-App purchases is as simple as flicking a switch. Select your target and open the ‘Capabilities’ tab. Find the In-App purchases section and turn them on:

Now we’re ready to start writing some code. Finally πŸ™‚

Some Code

I assume you already have your own project, and that you’ll be integrating your In-App purchases into it. So I’ll just cover the code and leave the UI up to you.

We’ll create a simple purchases controller and we’ll be using closures because they’re a bit more convenient than the delegate pattern. This will be the interface of our purchases controller:

protocol PurchasesControllerProtocol {
    var canMakePayments: Bool { get }
    
    func buyItem(withProductID productID: String)
    func restorePurchases()
    
    var onPurchase: ((String) -> ())? { get set }
    var onRestore: ((String) -> ())? { get set }
    var onFail: ((String) -> ())? { get set }
    var onCancel: (() -> ())? { get set }
}

It’s quite simple, really. We have a property ‘canMakePayments’ that you can use to enable/disable your ‘buy’ button. It’s important that you do this, because your app might get rejected if you allow your users to tap on a button if the purchases have been disabled (for example, with parental controls you can disable the purchases). ‘buyItem’ is self-explanatory. You pass in the product ID that you defined when you were creating your In-App purchase item on iTunes Connect. When you call the function, one of the closures will get called when the purchase is processed. You must provide your users with the ability to restore the purchases, therefore the ‘restorePurchases’ method.

In the initializer of our controller we’ll pass in an array of product identifiers, and we’ll validate them:

init(withProductIDs productIDs: [String]) {
    self.productIdentifiers = productIDs
        
    super.init()
        
    self.validateProductIdentifiers(productIdentifiers)
        
    SKPaymentQueue.default().add(self)
}

In the validate function, we create the request and start it:

private func validateProductIdentifiers(_ identifiers: [String]) {
    self.productRequest = SKProductsRequest(productIdentifiers: Set(identifiers))
    self.productRequest?.delegate = self
    self.productRequest?.start()
}

Then in the delegate callback we’ll get our SKProduct items that we’ll store in a local array:

extension PurchasesController: SKProductsRequestDelegate {
    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        self.products = response.products
    }
}

Now we can start buying stuff πŸ™‚ ‘buy’ function is simply filtering the array of products for the product with the passed in ID and creating a payment:

public func buyItem(withProductID productID: String) {
    guard let product = products?.filter({ return $0.productIdentifier == productID }).first else {
        return
    }
    buy(product)
}
    
private func buy(_ product: SKProduct) {
    let payment = SKPayment(product: product)
    SKPaymentQueue.default().add(payment)
}

Now we just need to process the response from the payment transaction:

extension PurchasesController: SKPaymentTransactionObserver {
    public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction in transactions {
            switch (transaction.transactionState) {
            case .purchased:
                processPurchased(transaction: transaction)
            case .failed:
                processFailed(transaction: transaction)
            case .restored:
                processRestored(transaction: transaction)
            case .deferred, .purchasing:
                break
            }
        }
    }
    
    func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
        onCancel?()
    }
    
    private func processPurchased(transaction: SKPaymentTransaction) {
        savePurchase(identifier: transaction.payment.productIdentifier)
        SKPaymentQueue.default().finishTransaction(transaction)
        
        onPurchase?(transaction.payment.productIdentifier)
    }
    
    private func processRestored(transaction: SKPaymentTransaction) {
        guard let productIdentifier = transaction.original?.payment.productIdentifier else {
            return
        }
        savePurchase(identifier: productIdentifier)
        SKPaymentQueue.default().finishTransaction(transaction)
        
        onRestore?(productIdentifier)
    }
    
    private func processFailed(transaction: SKPaymentTransaction) {
        SKPaymentQueue.default().finishTransaction(transaction)
        
        onFail?(transaction.payment.productIdentifier)
    }
    
    private func savePurchase(identifier: String?) {
        guard let identifier = identifier else {
            return
        }
        UserDefaults.standard.set(true, forKey: identifier)
        UserDefaults.standard.synchronize()
    }
}

In the first method, we’re processing our transactions. We’re also processing restoring of purchases here. The rest of the methods are pretty straight forward. We need to save our purchases on the device. In my case I’m using user defaults for it, this might not suit your use case. You can persist them any way you like, as long as you persist them πŸ™‚

Example Usage

Here is how you might want to use this controller:

var purchasesController: PurchasesControllerProtocol = PurchasesController(withProductIDs: ["com.mydomain.app.purchases.removeads"])

purchasesController.onPurchase = {
	(itemID) in
	// purchase successful
}

purchasesController.onRestore = {
	(itemID) in
	// purchases restored
}

purchasesController.onFail = {
	(itemID) in
	// purchase failed
}

purchasesController.onCancel = {
	// purchase cancelled
}

// buy something
purchasesController.buyItem(withProductID: ["com.mydomain.app.purchases.removeads"])

These are different code snippets, obviously you’ll have them in the appropriate places in your app. You might want to store your product IDs as constants somewhere πŸ™‚

Testing Purchases

You created those sandbox users previously. You’ll be using them to test your purchases. Because we’re using Non-Consumables, one sandbox user can perform only one purchase. So you might want to create a few test users. Before testing, you actually have to log out from iTunes. Go to your iPhone settings, find ‘iTunes & App Store’, tap on your ID at the top, and select ‘Sign Out’:

Now when you build and run your app, and select the ‘buy’ button, you’ll be prompted to login to your iTunes account. Login with your sandbox test account. You should see an alert prompting you to purchase an item (the alert will have the words ‘sandbox environment’ at the bottom). That should be it. Once your purchase is successful your closure should get called.

Just a note. If you’re creating your sandbox testers now, it will take a few minutes for the test accounts to become available, so you might not be able to login/purchase straight away.

There you go, that wasn’t so painful, and now you can get rich πŸ™‚

Conclusion

Using In-App purchases in your app is pretty straight forward, but setting them up on iTunes Connect can be a bit of a chore, as you’ve seen. Good thing is, once you have it up and running, adding more items is simple enough.

I usually include a full example project with tutorial posts like this one, but in this case it didn’t make sense to do so. I assume you already have an app and you want to add In-App purchases to it. So all you need is a simple controller that will handle that for you. You can get the purchases controller that we used in this post on my GitHub account.

I hope you learned something new today, and I hope you’ll be making some money with your awesome apps. As usual…

Have a nice day πŸ™‚

Dejan.

More resources

10 thoughts on “Using In-App Purchases

  1. David

    Hi Thanks for your tutorial. My in app purchase is not working. I followed your instructions. I am just getting the login, then it just fails (I put a print in the fail section which triggers) and nothing else happens. Any ideas? David

    Reply
    1. Dejan Agostini Post author

      Hey, thanks David πŸ™‚ I have a few ideas… Are you using that In-App purchase controller from the examples? Did you accept the paid applications agreement? Did you verify your sandbox user email account?

      Reply
  2. David

    Thank you so much Dejan, I really appreciate your help. It was my error in that I had not verified the sandbox account. πŸ‘

    Reply
  3. David

    Hi again, πŸ˜€
    Can I ask about restoring a purchase please?
    So if I have a button for restoring and the person moved to a new phone – how would I ensure that they are able to restore purchases?

    Could I use:

    purchasesController.onRestore = {
    (itemID) in
    // purchases restored
    }

    Would this be sufficient? Do I need to add the ProductID: eg [“com.mydomain.app.purchases.removeads”])
    Could you clarify what (itemID) is and how I would use it?
    Thanks
    David

    Reply
    1. Dejan Agostini Post author

      Hey David πŸ™‚
      Sure. When you initialise the purchases controller you pass in the product IDs. You verify those IDs using StoreKit, so when you call restore purchases StoreKit already has all those IDs and will restore the items that have been purchased.

      To answer your question… Yes, you could use purchasesController.onRestore = … And you don’t really need the itemID. The itemID is the productID that you define on iTunesConnect when you create your InApp purchase items.

      I hope that answers it. Let me know if I missed something, and if it works for you.

      As always, have a nice day πŸ™‚
      Dejan.

      Reply
    1. Dejan Agostini Post author

      Hi Ron,

      Thanks πŸ™‚ You’re right, I didn’t cover receipt validation in this article. It was getting too big and I was thinking of writing something up just to cover that topic.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.