The StoreKit framework has been around for many years and I actually enjoy working with it. As with many other frameworks, such as Core Data, you need to understand the ins and outs of the framework to avoid common mistakes. In this tutorial, I focus on five common mistakes developers make when using the StoreKit framework in a Cocoa project.

Observing the Payment Queue

The SKPaymentQueue class is a key member of the StoreKit framework. Your application uses it to communicate with the App Store, process purchases, and observe the state of the transactions associated with those purchases.

A mistake developers sometimes make is caching transactions or storing transactions for later use. Apple emphasizes that the payment queue your application has access to is the single source of truth with regards to the transactions the user makes in your application. What does that mean for you, the developer?

Trust the Payment Queue

The transactions you access through the payment queue are the only transactions that your application should care about. Observe the payment queue and respond to the transactions your application receives. Don't keep a separate cache of transactions for later use for whatever reason.

Observe the Payment Queue

The default payment queue your application has access to through the default() class method is accessible as soon as your application is launched by the operating system. It is important that you add an observer to that payment queue as soon as your application becomes active to ensure it doesn't miss any transactions.

Apple even suggests to add an observer in the application(_:didFinishLaunchingWithOptions:) method of the UIApplicationDelegate protocol. That is not a bad idea. I usually create an instance of the StoreKitManager class, a custom class I use for handling In-App Purchases, in the application(_:didFinishLaunchingWithOptions:) method and add the StoreKitManager instance as an observer of the payment queue.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    storeKitManager = StoreKitManager()

    return true
}
import StoreKit

class StoreKitManager: NSObject {

    // MARK: - Initialization

    override init() {
        super.init()

        SKPaymentQueue.default().add(self)
    }

}

extension StoreKitManager: SKPaymentTransactionObserver {

    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        // ...
    }

    func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
        // ...
    }

    func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
        // ...
    }

    func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
        // ...
    }

    func paymentQueue(_ queue: SKPaymentQueue, updatedDownloads downloads: [SKDownload]) {
        // ...
    }

}

The StoreKitManager instance isn't a singleton. It gets passed to the objects that need access to the information and processes it manages. If you are a regular reader, then you know that I am allergic to singletons.

Receipt Validation

If you want to save your application from being pirated or you need to verify that an In-App Purchase is legitimate, validation of the App Store receipt is essential. You have two options:

  • You validate the receipt locally on the device.
  • You send the receipt to Apple's servers for validation.

Validating the receipt locally has many advantages, but it can be a headache to implement. It may be easier or more convenient to send the receipt to Apple's servers, asking to validate it for you.

You can do this by sending the receipt to https://buy.itunes.apple.com/verifyReceipt in the production environment or https://sandbox.itunes.apple.com/verifyReceipt in the sandbox environment. You should never send the App Store receipt directly to Apple's servers. This is something you absolutely need to avoid.

It is easy for a third party to tamper with the response of Apple's servers. This also means that the shared secret you generate in iTunes Connect can be compromised.

Generate A Shared Secret in iTunes Connect

Generate A Shared Secret in iTunes Connect

The solution is simple. Send the App Store receipt to a server you control and have that server make the request to Apple's servers to validate the App Store receipt. This ensures the shared secret stays secret and, if implemented correctly, you make it harder for third parties to interfere with the validation of the App Store receipt.

Restore Purchases

Applications that offer In-App Purchases need to include the ability to restore previous purchases. Customers need to have the option to restore non-consumables and auto-renewable subscriptions. If your application doesn't include this ability, the App Review team will reject your application. Why is this important?

If a user unlocks a feature in your application, removes the application from their device, and installs the application again a few weeks later, they need to have the possibility to restore the purchases they made in your application. That is Apple's policy and I am sure you agree it makes sense.

Fortunately, adding the ability to restore previous purchases is easy. The SKPaymentQueue class of the StoreKit framework defines a method to restore previous purchases. When a transaction finishes, it is removed from the payment queue and it is no longer accessible to your application. By invoking restoreCompletedTransactions() on the default payment queue, the latter replays past transactions. This gives your application the opportunity to restore previous purchases by observing the default payment queue I mentioned earlier.

It isn't a good idea to restore previous purchases without the user's explicit consent. Whenever you invoke restoreCompletedTransactions(), the user needs to enter the password of their Apple ID. You don't want to show this dialog without the user knowing or understanding why this is necessary.

Validate the Receipt Yourself

While it is great that several libraries include the ability to validate an App Store receipt for you, this does pose a risk. By using an off-the-shelf solution, you run the risk that people with bad intentions bypass the security you have put into place. The more people using a library, the more risk you run.

That is why Apple recommends to roll your own implementation. Because it involves certificate validation and decrypting the App Store receipt, it is something many developers prefer to sidestep.

Rolling your own solution also applies to the tools you use to validate the App Store receipt dependencies, such as OpenSSL. Don't include a static library you found on the web. Build OpenSSL from source instead. This shouldn't be hard. CocoaPods, for example, makes this painless and straightforward.

I agree that, for many developers, validating the App Store receipt isn't a walk in the park. But if your business depends on the revenue it makes from In-App Purchases, it is vital that you make sure only legitimate purchases go through.

Validating the App Store receipt on Apple's servers is another option. If your application already communicates with a custom backend, then that is certainly an option to consider.

Show the Right Price

As I mentioned earlier, the StoreKit framework isn't hard to use. But you need to know and understand how it works and where to find the information you need.

A common example is pricing. If you offer an item for sale in your application, make sure you show the user what the item is going to cost them and display that price in the user's locale. This is much easier than you might think.

The SKProduct class defines two properties that help you with this:

  • price of type NSDecimalNumber
  • priceLocale of type Locale

Using the values of these properties, it is straightforward to show the user the price in their locale. Take a look at the example below.

extension SKProduct {

    var localizedPrice: String? {
        // Initialize Number Formatter
        let numberFormatter = NumberFormatter()

        // Configure Number Formatter
        numberFormatter.locale = priceLocale
        numberFormatter.numberStyle = .currency

        return numberFormatter.string(from: price)
    }

}

In the extension for the SKProduct class, we implement a computed property, localizedPrice, of type String?. We create a NumberFormatter object, set its locale and numberStyle, and use it to format the value of the price property of the SKProduct object. Because the string(from:) method of the NumberFormatter class returns an optional, the type of the localizedPrice computed property is of type String?.

It's in the Details

In-App Purchases aren't as daunting as you might think. It is true that it involves several components working together, but the basics are easy to understand and implement.

Apple's sandbox environment offers you the opportunity to test your implementation, making sure your customers don't run into problems you didn't foresee.