iOS Introductory Prices
New tools and new burdens for subscription app developers
In iOS 11.2 Apple introduced yet another improvement to the in-app subscriptions system on iOS: introductory prices. Introductory prices are an excellent addition to the iOS developer’s tool belt, however, Apple was sure to add in some of their signature pointless burden. I wrote this guide to illustrate some of the possibilities and to help navigate the unnecessarily difficult.
Introductory Price Types
There are three types of introductory price available: Pay as you go, Pay up front, and Free trial. Each one provides a different buyer’s experience.
Pay as you go
As its name hints, Pay as you go charges users a reduced rate for some number of renewal periods before increasing to the regular price.
To configure a product for Pay as you go you need to specify the price and the number of periods via iTunes Connect. The introductory price must be less than the regular price of the subscription. The available number of periods differ depending on the duration of the subscription.
Intro period durations must be the same as their regular product durations; you can’t have a one-week Pay as you go period that leads to a monthly subscription. The maximum duration for an entire introductory period is one year, except in the case of a one-week subscription where the maximum is 12 weeks.
Pay up front
Rather than providing a lower price for multiple renewal periods, Pay up front charges any price for one period of a specified duration before the regular price takes over.
Pay up front has two parameters the developer can modify: the price and the duration. Unlike a Pay as you go price, which must be less than the regular price, Pay up front periods can use any price. This means you could use intro prices to do something like charge a one time high cost period, followed by short renewals.
I think the most viable use of Pay up front is something like the Comcast model: 1 year up front at a reduced rate, then month-to-month after that.
Ah, our good old friend Free trial. It has been around for a while now but, in a good move to combat API entropy, Apple has made the current Free trial one of the new introductory price types. The behavior mimics Pay up front, but with a $0.00 price and some additionally available durations: three days, one week, and two weeks. The old API for Free trial still exists, but I am sure Apple will deprecate it in the not too distant future.
Introductory Pricing StoreKit APIs
The new introductory pricing requires support on the iOS side to function. You must display to a user the introductory price and details about the offer in your user interface. To do this, Apple added an optional
introductoryPrice property to
SKProduct in iOS 11.2. If a product has an introductory price, this property will be non-nil.
SKProduct.introductoryPrice returns an object of a new class,
SKProductDiscount, that contains all the information specified when configuring the intro price:
priceLocale, used for presenting the price to the user
paymentModeenum that is either
numberOfPeriodsthat is used for
payAsYouGoto tell the user the number of periods before the regular price kicks in, always 1 for
subscriptionPeriodthat specifies the duration of a subscription period, i.e. 1 month, 2 months etc.
This data should be used in your payment flow to display the introductory pricing details to the user. Just like prices, you can’t rely on hardcoded knowledge of the products in your app (beyond the product identifiers). This data must be fetched from StoreKit before it can be displayed.
Determining introductory price eligibility
Here be dragons. Once a user completes an introductory pricing period in a subscription group, they are no longer eligible for introductory pricing for any other product in that same group.
When a user initiates a purchase, the App Store system UI will pop-up and present the correct price, either the intro or regular price. However, Apple did not provide us with this same sacred knowledge. We have to figure this out on our own in order to present correct prices to our users.
Apple’s instructions for this seem simple enough:
But there is actually a fair bit to unwrap here.
You now have to validate a receipt before a user can see a product’s price.
Validating first is complicated. It requires you to possibly refresh the receipt, which can trigger an App Store login prompt, and to do the actual receipt validation either locally or by sending it to a service. Verifying the receipt before a product is even visible adds yet another thing for developers to screw up. (Which we are known to do from time to time.)
Once you have parsed and validated the receipt, you can see what products the user has purchased. From there you know for what products an introductory period has been completed and can then determine for which subscription groups introductory pricing is unavailable. To compute the eligibility with certainty also requires knowing to which subscription group every product belongs. Neither the
/verifyReceipt response nor
SKProduct will tell you the group for a subscription product. You will now have to have to store both the group and the product in your app or on your backend to verify eligibility correctly.
Why does it have to be so hard?
All of this added complexity increases the time it takes to show the user a product’s price when they land on the purchase page of your app. You now need to:
- Read, and possibly refresh, the App Store receipt
- Verify the App Store receipt with Apple
- Fetch product info using an
This will add 1, maybe 2, additional remote calls and possibly add seconds to the time it takes to display products to your users. Time spent loading is sales lost.
It doesn’t have to be like this
The system purchasing UI always shows the correct price when initiating a purchase, so we know that they know the correct price. Why didn’t they just give us that information?
SKProduct.isEligibleForIntroductoryPricing is all we needed.
I suspect it comes down to some combination of internal politics and technical debt but, it’s crazy to me that such an API shortcoming would ship. The developer experience around StoreKit and IAPs is already so broken, and this just adds one more thing to a developer’s plate. Neglect of the developer experience runs downhill. A small increase in complexity for the developer leads to a large increase in poor user experiences.
Looking for a way out?
I wouldn’t be a good salesman if I didn’t also have a solution to the problem. RevenueCat just added support for introductory pricing and along with that we also added support for checking intro pricing eligibility to the RevenueCat iOS SDK. We have been able to condense it down to one call that makes the process smoother for developers.
How to make introductory prices great
Introductory prices are a welcome addition to the in-app subscriptions ecosystem. The API is well designed and sensible, and the available product configurations are well thought out and very flexible. We just need a couple of things to make them great:
- Add an
isEligibleForIntroPricesmethod or property to
- Or, add a product’s group to
Either of these would go a long way to making sure implementations of introductory prices do not create a substandard iOS user experience.