A Guide to iOS Introductory Prices

New tools and new burdens for subscription app developers.

A Guide to iOS Introductory Prices
Jacob Eiting

Jacob Eiting

PublishedLast updated

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.

Update: As of iOS 12.0, SKProduct now includes a subscriptionGroupIdentifier property, so it’s now possible to compute Introductory Pricing eligibility locally. More details are available at the bottom of this post.

Update 2: It looks like Apple finally listened and introduced a new method called isEligibleForIntroOffer, which allows you to bypass the implementation pains and get the value directly from StoreKit. The bad news: it’s only compatible with iOS 15+, tvOS 15+, watchOS 8+, macOS 12+. If your app targets older OS versions, keep reading to see how to implement eligibility checks. To perform the checks with the new method, you:

  • Get the products by calling products(for:)
  • For a given product, check product.subscription.isEligibleForIntroOffer

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.

Image for post

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.

Image for post

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.

Image for post

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.

Image for post

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.

Image for post

Free trial

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.

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/

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.

SKProductDiscount

SKProduct.introductoryPrice returns an object of a new class, SKProductDiscount, that contains all the information specified when configuring the intro price:

  • price and priceLocale, used for presenting the price to the user
  • paymentMode enum that is either payAsYouGo, payUpFront, or freeTrial
  • numberOfPeriods that is used for payAsYouGo to tell the user the number of periods before the regular price kicks in, always 1 for payUpFront and freeTrial
  • subscriptionPeriod that 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:

Image for post

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:

  1. Read, and possibly refresh, the App Store receipt
  2. Verify the App Store receipt with Apple
  3. Fetch product info using an SKProductsRequest

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 isEligibleForIntroPrices method or property to StoreKit
  • Or, add a product’s group to SKProduct or the /verifyReceipt response.

Either of these would go a long way to making sure implementations of introductory prices do not create a substandard iOS user experience.

Update: 

As of iOS 12.0, `SKProduct` now includes `subscriptionGroupIdentifier` property, so it’s now possible to compute Introductory Pricing eligibility locally.

While this update is great, calculating intro eligibility locally still requires a lot of unnecessary dev work.

To check intro eligibility on iOS >= 12.0, you need to: 

  • Refresh the receipt so you have updated information about the user’s purchases
  • Parse the receipt and look for purchases made with introductory pricing
  • Transform the identifiers for purchases made with intro pricing into `SKProducts`
  • Transform the productIdentifiers you want to check eligibility for into `SKProducts`
  • Check if there’s a match – if there’s at least one purchase made using intro pricing for the same product identifier or the same subscription group, the user is not eligible. If none are found, they’re eligible.

You will also need to be able to fall back to a backup solution that uses a server if the user’s OS is older than iOS 12.0.

Our hope is that Apple will one day add an `isEligibleForIntroPrices` property to StoreKit, which would make this feature very easy for developers to use.

Update 2:

It looks like Apple finally listened and introduced a new method called isEligibleForIntroOffer, which allows you to bypass the implementation pains and get the value directly from StoreKit. The bad news: it’s only compatible with iOS 15+, tvOS 15+, watchOS 8+, macOS 12+. If your app targets older OS versions, you will still have to resort to the old, convoluted way of performing these checks. Or let us do it for you! Our purchases-ios SDK version 4 automatically determines which version of introductory eligibility calculation to use depending on OS version availability.

Additional Reading

Offering Introductory Prices in Your App

Introductory Pricing

SKProductDiscount documentation

Pricing and availability for Introductory Prices

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

Cache at RevenueCat
Engineering

Scaling smoothly: RevenueCat’s data-caching techniques for 1.2 billion daily API requests

A deep-dive into the techniques that fuel our efficient cache management.

Guillermo Pérez

Guillermo Pérez

October 31, 2023

Engineering

How to use offering metadata to A/B test your paywall with Experiments

Even if you’re using a custom paywall, you can run experiments with RevenueCat

Charlie Chapman

Charlie Chapman

August 31, 2023

Engineering

How to use StoreKit views to build a subscription app paywall with SwiftUI

A guide on Apple’s new StoreView, ProductView, and SubscriptionStoreView APIs for building native paywalls for your subscription app.

Charlie Chapman

Charlie Chapman

August 25, 2023

Want to see how RevenueCat can help?

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

Olivier Lemarie, PhotoRoomOlivier Lemarie, PhotoRoom
Read case study