StoreKit is Broken

Apple's framework is fundamentally flawed.

StoreKit is Broken
Jacob Eiting

Jacob Eiting

PublishedLast updated

StoreKit is Apple’s framework for doing in-app purchases that was introduced in iPhone OS 3.0 in July of 2009. It provided developers with a way of monetizing their apps besides charging to download. This was a game changer. Despite its success, the framework has a very flawed design that has existed unchanged since iPhone OS 3.0.

An Observational Approach

The StoreKit API uses a queue and an observer. A developer requests a purchase be added to the queue and the observer is notified when that request changes state. Typically you have a method that looks something like this:

1func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
2        for transaction in transactions {
3            switch(transaction.transactionState) {
4            case .purchasing:
5                break;
6            case .purchased:
7            case .restored:
8                handlePurchase(transaction);
9            case .failed:
10                handleFailure(transaction);
11            case .deferred:
12                break;
13            }
14        }
15    }
16}

‍‍Have a look at the following diagram from Apple’s IAP programming guide.

Apple in-app purchase diagram
How Apple expects you to integrate StoreKit

The problem lies in connecting the “Makes payment request” and the “Observer is called” steps of Apple’s diagram. For a good user experience, your app needs to react correctly to purchases that it initiates, but you can’t.

StoreKit’s design makes reacting correctly impossible.

SKPayment is delivered to the payment queue with addPayment(), the return type is void. StoreKit returns nothing for you to track your payment. When your transaction appears in the payment queue observer method, it has lost all connection to the SKPayment that generated it. This makes it impossible to associate payments with the transactions they generate.

Guessing Around The Issue

So how do developers get around this limitation? Many dive into the CocoaPods library and grab for some of the very popular wrappers like SwiftyStoreKit. It provides a very nice block based wrapper around StoreKit, transforming addPayment into addPaymentWithCallback. How awesome!

But it is a fraud.

SwiftyStoreKit and any of the other StoreKit convenience frameworks, will call the callback block of the first transaction with a matching productIdentifier. This doesn’t work. If there are failed transactions on the queue that weren’t finished they could show up on the queue as soon as a user types in their App Store credentials. This causes SwiftyStoreKit to call your callback with a failed transaction when you may actually have a successful transaction waiting on the queue. This leads to a frustrating experience for the user at the worst time that will likely incur you review problems and customer support costs.

So how do you do it correctly? You can’t. If you are disabling the UI for your application you need to listen for any failed transactions and assume those were generated by your request and re-enable your UI. This means that a good transaction following a failed transaction will create a very confusing user experience.

Subscribe to the Sub Club newsletter

How to Fix StoreKit

The fix is simple. Returning the SKPaymentTransaction (or something related to it) from addPayment would fix the problem. This would allow block based wrappers to be written and make implementing UI for IAP much simpler. This improved API would pass on its benefits to users, resulting in higher customer satisfaction and fewer customer service tickets for Apple.

But after 8 years of almost no changes to StoreKit, I’m not holding my breath.

RevenueCat is the best way to implement subscriptions in your mobile app. We handle all the complicated parts so you can get back to building. Request an invite today at https://www.revenuecat.com/

In-App Subscriptions Made Easy

See why thousands of the world's tops apps use RevenueCat to power in-app purchases, analyze subscription data, and grow revenue on iOS, Android, and the web.

Related posts

Google Play class action developer lawsuit
Introducing Google Play Billing Library 7: New Features and Migration Guide
Engineering

Introducing Google Play Billing Library 7: New Features and Migration Guide

Everything you need to know about Google Play Billing Library 7

Rik Haandrikman

Rik Haandrikman

May 14, 2024

What is SKErrorDomain Error 0 and what can I do about it?
Engineering

What is SKErrorDomain Error 0 and what can I do about it?

What to do when seeing SKErrorDomain Error code 0 from StoreKit on iOS.

Charlie Chapman

Charlie Chapman

April 24, 2024

How we solved RevenueCat’s biggest challenges on data ingestion into Snowflake
How we solved RevenueCat’s biggest challenges on data ingestion into Snowflake
Engineering

How we solved RevenueCat’s biggest challenges on data ingestion into Snowflake

Challenges, solutions, and insights from optimizing our data ingestion pipeline.

Jesús Sánchez

Jesús Sánchez

April 15, 2024

Want to see how RevenueCat can help?

RevenueCat enables us to have one single source of truth for subscriptions and revenue data.

Olivier Lemarié, PhotoroomOlivier Lemarié, Photoroom
Read Case Study