Streaming is available in most browsers,
and in the Developer app.
-
What’s new in StoreKit and In-App Purchase
Learn how to build and deliver even better purchase experiences using the App Store In-App Purchase system. We'll demo new StoreKit views control styles and new APIs to improve your subscription customization, discuss new fields for transaction-level information, and explore new testability in Xcode. We'll also review an important StoreKit deprecation.
Chapters
- 0:00 - Introduction
- 0:36 - Core API enhancements
- 3:12 - Merchandise using SwiftUI
- 15:35 - Test in Xcode
- 21:06 - Update to StoreKit 2
Resources
- Forum: App Store Distribution & Marketing
- In-App Purchase
- Introducing StoreKit 2
- Message
- Original API for In-App Purchase
- Product.SubscriptionInfo.RenewalInfo
- Setting up StoreKit Testing in Xcode
- StoreKit views
- Testing in-app purchases with StoreKit transaction manager in Xcode
- Transaction properties
Related Videos
WWDC24
WWDC23
WWDC22
WWDC21
WWDC20
-
Download
Hi, I’m Rudy, and welcome to “What’s new in StoreKit and In-App Purchase”. I’m excited to share the new features in StoreKit and the enhancements we made to the testing experience in Xcode to help you test these using your StoreKit configuration. First, I’ll review the new core framework features. Then, I’ll share some new ways you can build your merchandising UI. I’ll also cover how you can test your app’s behavior within Xcode.
Finally, I’ll go over some of the benefits of updating your app to use StoreKit 2. To kick things off, let’s talk about the updates we’re bringing to your customer’s transaction history. Historically, the transaction history APIs have included transactions for auto-renewable subscriptions, non-renewing subscriptions, non-consumables, and unfinished consumables. This means when someone using your app purchased a consumable, the finished transaction for that purchase was not accessible through these APIs. Starting in iOS 18, the transaction history APIs include finished consumable transactions.
Now, instead of having to manually track finished consumables, the framework provides transactions for all consumables, regardless of their finished state. This is a new opt-in feature that can be configured via your project’s info.plist file.
To begin receiving transactions for finished consumables, set the value of the new SKIncludeConsumableInAppPurchaseHistory key to true. Then, listen for transactions as you normally would.
Finished consumables are also available from the App Store Server API. We also added new fields to the Transaction and RenewalInfo data models, to provide you additional in-app purchase transaction-level information The first new field in the Transaction type is currency, and as its name suggests, it tells you the currency used at the time of purchase. This field goes together with the new price member, which contains the price you configured in App Store Connect.
For the renewal info model, we added two new fields which mirror the fields added to the Transaction model. The first new field is called currency, and the second new field is called renewalPrice. Renewal price indicates the amount that will be charged to the customer when this subscription renews. It's important to note that the renewal price should not be interpreted without the context provided by the new currency member. You can access these new transaction and renewal info APIs when building your app with Xcode 16. They're even available when your app is running on older OS versions, as far back as iOS 15. Refer to the in-app purchase documentation for guidance on how to access these fields if your app supports running older OS versions. The last update we’re bringing to the core API this year is a new type of subscription offer called win-back offers. Win-back offers are designed to help you recover churned subscribers. Integrating this new offer type into your app is a breeze with powerful new tools that allow you to customize eligibility rules from App Store Connect, and merchandise the offer with the StoreKit Message API without requiring any additional code.
And win-back offers may be promoted on the App Store. For example, our editorial team may feature your win-back offers when they build personalized recommendations for customers on the Today, Games, and Apps tabs. To learn more about win-back offers, check out the WWDC24 session “Implement App Store Offers”. Now, let’s take a look at some of the new ways you can merchandise your in-app purchases. Since we introduced StoreKit views last year at WWDC23, we’ve been really excited about the great in-app purchase UI people have been building. We’ve been listening to your feedback and we have great new ways for customizing your subscription store view. And what better way to showcase these cool new features than to build the subscription upsell for our favorite streaming app: Destination Video. Since I’ll be using Xcode previews to rapidly iterate on our merchandising UI, I’ve already set up a StoreKit configuration file with our product metadata. This is a necessary step to get In-App Purchase UI working with Xcode previews. For a more detailed explanation on how to set up a StoreKit configuration file in Xcode, check out “What’s new in StoreKit testing” from WWDC22 and “Introducing StoreKit Testing in Xcode” from WWDC20. The subscription I’m building for Destination Video offers customers two distinct choices when subscribing.
Access to a Premium plan or a Basic plan at a reduced price. Then, you can choose whether your subscription renews every month, or every year. I’ll show you how to create a subscription store that merchandises these plans in Xcode. I’ve already created a view called DestinationVideoShop, which I’ll use to create our subscription store. The first step is to declare a SubscriptionStoreView, and provide the group ID for our subscription.
With just this one line of code, we’re off to a great start. But I’d like to structure my store so the different levels of service are clearly visible to my customers. To achieve this, I can declare a subscription option group for each of the premium and basic levels of service. To get started, I’ll add a subscription option group within the subscription store.
You define a subscription option group using a condition representing which products are included. I want this first group to represent both premium options, so I declared the condition to include products whenever the level of service is premium.
To make my subscription store easier to work with, I created an enum earlier called StreamingPassLevel, which I’m using to model the premium and basic levels of service. Notice how the store updated to only display premium plans, which matches the condition I declared the group with.
You can also add a label to the group. In this case I’ll use premium.
Things start to get interesting when I declare a second group, so I’ll go ahead and add a group representing the basic options.
Now that I’ve declared two groups, the subscription store view automatically creates a tab view, allowing me to view either the basic, or premium plans.
This new layout makes my subscription choices a lot easier to understand.
One important feature about the SubscriptionStoreView is that it allows you to provide a SwiftUI view as custom marketing content. Normally, you provide this view directly to the SubscriptionStoreView, but since I’m declaring a hierarchical structure, I provide my marketing content directly to the groups.
Notice the preview updates to explain the value of my service. Using subscription option groups is great, since it allows us to provide a different marketing content view depending on the active group. To demonstrate this, I’ll add a modified view explaining the value of the basic plan.
Now, when I change the active tab the marketing content explains the basic plans.
Declaring individual option groups is very expressive, because the declaration looks very similar to the UI appearance. For cases like this, you can streamline your implementation by declaring a set of subscription option groups.
Now, I just provide the StreamingPassLevel value corresponding to each product, instead of a condition.
The group set represents a group for each unique value I return from this first closure, and here, I’ve returned one group for premium and one for basic.
This also makes it easier to declare our marketing content, since we can just provide the streaming pass level to our marketing content view.
Streaming Pass+ offers a lot of value to customers, so I want to make sure my marketing content reflects this.
Now, my marketing content is very informative.
But it uses a very large portion of the screen space. To reduce the height of the store, I can use the subscriptionStoreControlStyle modifier, and use the new compact picker style.
Now, the subscription option picker takes up much less space than before. We can make it even easier to discover the other plan options by placing the controls in the bottom bar. By default, only the subscribe button is contained in the bottom bar. Since the control style is so compact, I can provide bottomBar as the placement parameter to the control style modifier.
Now, the subscription options are always visible in the bottom bar, even as I scroll through the service benefits in our marketing content.
So that’s how you can use the powerful new subscription option group API, and its convenience API subscription option group set. You’ve also seen the new compact picker control style, and the control placement API. But that’s not all we’re bringing to StoreKit views this year. I have numerous updates to share with you, so let’s get started. I just showed you how we can use subscription option groups to declare the structure of our store’s content. The store we built used the tabs style to draw our subscription option groups, which is great for when you have clear distinctions among your subscription plan options, like different levels of service.
Tabs is one of the styles available for presenting subscription option groups. The group style you choose defines how StoreKit presents your groups, and you set it using the subscriptionOptionGroupStyle modifier on your SubscriptionStoreView. And this isn’t the only style you can choose from. You can also use the links style. Links are great for when you want to present more plan options to your customers within your navigation container. If you don’t have one, a sheet will pop over your store with your other plan options whenever the navigation link is tapped on. Subscription option groups are the building blocks of organizing your subscription options into a hierarchy you define. This is a simple, yet powerful API that lets you create groups within groups and even inline groups.
We also created the group set API that lets you create multiple groups in one declaration. And for the most common use cases, such as grouping by subscription period, we created convenience APIs like SubscriptionPeriodGroupSet.
Earlier, I showed how you can use the placement API when building the Destination Video store. This is a new API that lets you choose where in the subscription store you’d like to place your subscribe controls.
It’s worth noting that not all placements are available to every control style. This is because some control styles have a layout that is only compatible with certain placements.
To make this API easier to hold, we designed it so that it statically tells you which placements are available to a given control style.
And because every platform has different design patterns, we introduced platform-specific placements that unlock new layouts, starting in iOS 18 and aligned releases To show you what I mean, let’s take a closer look at the control style modifier when developing for tvOS 18. On tvOS, the buttons style is the only standard control style available for you to use.
However, this control style has a number of placements available to it, including automatic, leading, trailing, and bottom. Let’s take a closer look at each of these, starting with the leading placement. New in tvOS 18, the subscription store view can be laid out horizontally, with the subscription controls positioned on the edge of the screen indicated by the placement value, and the marketing content placed on the edge opposite the controls. Here, I used the leading placement, but I can also use the trailing placement to access the new horizontal layout as well. The trailing placement is the new default for apps built with the tvOS 18 SDK. And, for cases where your marketing content fits better in a compact vertical height, you can use the bottom placement.
The control placement API can be used with any of the existing control styles you’re already familiar with. Speaking of control styles, I’m excited to share three new standard styles in iOS 18. Let’s take a closer look at each. First is the compactPicker style I showed earlier. This is the first picker-type control style that has a horizontal shelf layout. The compact size of this style makes it ideal for highlighting obvious differences between your plans; for example, if your subscription plans have different durations, but they all unlock the same level of service.
Since the compact picker style prefers to display your subscription options on the screen all at once, it is best used when your subscription store only displays two or three plan options, so that your customers don’t have to scroll through your store.
The next new style is the pagedPicker style. Similar to the compact picker style, this style also uses a horizontal layout, with the difference being that the compact picker is meant to show 2 to 3 plans in a row, where the paged picker has a horizontal paging effect and displays more detail about the plans. Last, is the pagedProminentPicker style. This style is most similar to the paged picker style, but adds a prominent border and scale effect to the selected subscription option. These new styles are great for reducing the usage of vertical space. This is ideal when you are merchandising just a few subscription plans, and you want to reserve some space in your store for other important design elements such as your marketing content.
The addition of these control styles increases the total number of standard styles you can choose from, from three to six Subscription store control styles are the essential elements of a subscription store, and these new standard styles paired with the placement and grouping APIs give you the ability to create best-in-class In-App Purchase experiences for your App Store apps. And, if you want to further customize the appearance of your subscription store, I’m happy to share that starting in iOS 18, you can create your own custom control styles. To help you build your custom styles, we’re also making available the same primitives StoreKit uses to create the standard styles you’re already familiar with. Let’s build a subscription store from scratch to show you how easy it is to write your own style implementation.
I’ll create a custom control style with a picker-type layout that adds a special badge for family shareable plans. The first step to creating a custom control style is to declare a type that conforms to the SubscriptionStoreControlStyle protocol. This protocol only has one required method, makeBody.
The configuration value passed to the make body function has all the information you need to create your custom control You can use this configuration value with the new SubscriptionPicker API.
The subscription picker expects two closures: for the first closure, you return the view that’ll be used to represent each of the vended subscription options. For this spec, I’ll create a custom SwiftUI view from scratch.
I’ll start by adding a VStack and the picker option’s display name.
I’ll add the option’s localized price display using a helper method I created earlier.
Then, I’ll check if the picker option has family sharing enabled. If it does, I add the family shareable badge Next, I’ll add the subscription option’s localized description.
To indicate which option is selected, I’m going to wrap this view in an HStack and add a selection indicator, passing it the selected state of the picker option. For the second closure, you’ll want to provide a view that gives the customer a way to subscribe to their selected plan. Semantically, this view is a button, in that when a user interacts with it the purchase for the selected option will be triggered. Similar to the first closure, you can either build our own subscribe button, or you can use the new SubscribeButton API, and pass it the option argument provided to the closure.
To use your new custom control style, all you need to do is modify your subscription store view with the control style modifier, and pass in an instance of your custom control style type. It’s that easy to use your own control style implementation with the SubscriptionStoreView. Custom control styles are a great way to leverage StoreKit’s powerful infrastructure, such as App Store data flow, while having full creative domain over how your subscription store merchandises your in-app purchases. I’ve shown you some new features in the SubscriptionStoreView. Now, I’ll show you some enhancements to the testing experience in Xcode that’ll make testing your App Store apps even easier. StoreKit Testing in Xcode is the best way to ensure your in-app purchase experience is the best it can be, from the moment you start building.
With StoreKit Testing in Xcode, you can defer configuring your products in App Store Connect until you’ve made sure your app behaves as expected, letting you focus your attention on developing the key features that make your app unique. And this year, we added numerous ways you can test your app from within Xcode, so let’s get into it. First, updates to the StoreKit configuration. New in Xcode 16, you can test your app’s privacy policy and license agreement locally. This new setting can be found in the StoreKit configuration file editor, under the new App Policies section. Clicking this option will open an editor for your app’s license agreement, and privacy policy. The values entered into these fields are displayed on your app’s SubscriptionStoreView when someone taps the terms of service and privacy policy buttons.
Also new in Xcode 16, you can test localizations for your subscription group’s display name. This new setting can be found by navigating to a subscription group in the StoreKit configuration editor. You’ll notice there’s a new Localizations section below the subscription plans included in the group. Clicking on the plus button toward the bottom edge of this section opens an editor where you can add your localized group display name.
We’re also adding a new configuration setting for win-back offers. Adding a win-back offer to your testing configuration is very similar to the other subscription offer types you’re already familiar with. To learn how to add one to your StoreKit configuration, check out the WWDC24 session “Implement App Store Offers.” Next, we’re adding support for you to test an in-app purchase image from your testing configuration. This new setting can be found in the product editor under the new “Image” section. Since the image you provide in your StoreKit configuration is for local testing purposes only, you can add any image you want. Of course, the easiest way to test what this image looks like in your app is to use ProductView or StoreView and set the prefersPromotionalIcon flag to true.
To learn more about ProductView, check out Meet StoreKit for SwiftUI from WWDC23. One more update to the StoreKit configuration file is a new section called Dialogs.
This setting lets you select whether system dialogs related to in-app purchase should be enabled or disabled.
By default, system dialogs are always enabled. Disabling system dialogs will automatically choose the default option whenever a dialog would be presented, such as during an in-app purchase. This is especially useful if you’re performing UI automation tests, or manual tests, and are only interested in testing the default flows of your app’s in-app purchase logic. Now, I’m going to show you the updates we’re bringing to the transaction manager in Xcode. The transaction manager is essential for testing and debugging your in-app purchases. Here, you can do things like inspect transactions and simulate different kinds of purchases for all apps that are installed from Xcode and use StoreKit Testing in Xcode. And, this works across all of your devices and simulators. Let’s head on over to Xcode, so I can show you the updates coming to the transaction manager. I have a simulator running Destination Video, which I’ll be using to demonstrate these new capabilities. Starting in Xcode 15.2, you can send purchase intents to your app directly from Xcode.
Your app receives a purchase intent when someone initiates a purchase outside of it, such as for a promoted product in the App Store.
The purchase data is then sent to your app, which you can use to complete the purchase. Testing how your app behaves when it receives a purchase intent is crucial, and can now be easily done without needing to go through the App Store. If you’d like to learn more about promoted in-app purchases and implementing purchase intents in your app, check out “What’s new in StoreKit 2 and StoreKit Testing in Xcode” from WWDC23. I’ve already added the code to listen for purchase intents in the Destination Video app, so let me show you how we can test it. To send a purchase intent to our app, I’ll start by clicking the plus indicator to the left of the filter bar in the transaction manager window. Under the list of your app’s configured products, you’ll notice there’s a new control to decide between a regular purchase and a purchase intent. Choosing a product to send a purchase intent for, setting the purchase type to Purchase Intent, and clicking Done will send a purchase intent to your testing device. To handle incoming purchase intents, I already built some custom UI using SwiftUI. If you choose to handle incoming purchase intents, you can also make your own UI to merchandise the product. If you do nothing, the payment sheet will present itself once your app launches, allowing the customer to complete the purchase flow. And that’s how you test purchase intents in Xcode. Also new starting in iOS 18, you can now test billing issue messages directly in app. Your app will receive a billing issue message when your subscription cannot renew due to a billing problem. In this case, StoreKit will prompt your customer to resolve the issue through a sheet. In Xcode you can test this with the transaction manager. If your device is running iOS 18, you will also get a sheet in the app where you can choose to cancel the subscription or resolve the issue. To test billing issues, enable the billing retry option in the configuration settings of your StoreKit configuration.
As you’re testing, when the subscription attempts to renew, you’ll see this badge appear next to the transaction, indicating it has now entered billing retry.
My simulator already received the message and has prompted a sheet to resolve the issue. I’m going to tap on resolve, which fixes the billing issue and shows the subscription to be renewed.
And that’s how you test billing issue messages from within Xcode. If your app still uses the Original API for In-App Purchases, I have an important update to share with you. Beginning with iOS 18 and aligned OS releases, the Original API for In-App Purchase is deprecated, including the unified receipt. Your existing apps will continue to work, but the legacy API won’t receive any enhancements or new features in future operating system releases. To provide the best in-app purchase experience, we strongly recommend updating your existing app to use StoreKit 2. StoreKit 2 offers many improvements over the Original StoreKit API. Such as your customer transaction history and subscription renewal info is always available to your app. And, the framework automatically performs cryptographic validation for you. StoreKit 2 also uses modern language features like the Swift async/await pattern to perform tasks like fetching product metadata purchasing, and retrieving your customer transaction history.
Finally, thanks to the @backDeployed attribute in Swift, StoreKit 2 lets you access new App Store features even when your app is installed on a device with an older operating system. And that’s not all. StoreKit 2 provides an entire suite of tools that makes handling in-app purchases really easy. To learn more about the APIs you can use in your app today, check out the StoreKit docs on the Apple Developer website. That’s everything I have for you today. Let’s quickly recap what I shared. Providing your customers the best in-app purchase experience starts by using StoreKit 2. Merchandising your in-app purchases is made easy, as I showed you with all the new features coming to the subscription store view. When you’re ready to test your in-app purchase flows, take StoreKit Testing in Xcode out for a spin. For more information about these and other StoreKit features check out our previous sessions: “Meet StoreKit 2“, “What’s new in StoreKit testing“, and “Meet StoreKit for SwiftUI.“ I’m excited to see what you build using StoreKit. Thanks for joining me!
-
-
4:26 - Destination Video Shop
import StoreKit import SwiftUI struct DestinationVideoShop: View { var body: some View { SubscriptionStoreView(groupID: Self.subscriptionGroupID) { SubscriptionOptionGroupSet { product in StreamingPassLevel(product) } label: { streamingPassLevel in Text(streamingPassLevel.localizedTitle) } marketingContent: { streamingPassLevel in StreamingPassMarketingContent(level: streamingPassLevel) StreamingPassFeatures(level: streamingPassLevel) } } .subscriptionStoreControlStyle(.compactPicker, placement: .bottomBar) } }
-
9:06 - Subscription Option Groups - Tabs style
SubscriptionStoreView(groupID: Self.subscriptionGroupID) { SubscriptionOptionGroupSet { product in StreamingPassLevel(product) } label: { streamingPassLevel in Text(streamingPassLevel.localizedTitle) } marketingContent: { _ in StreamingPassMarketingContent() } } .subscriptionStoreControlStyle(.compactPicker, placement: .bottomBar) .subscriptionStoreOptionGroupStyle(.tabs)
-
9:20 - Subscription Option Groups - Links style
SubscriptionStoreView(groupID: Self.subscriptionGroupID) { SubscriptionOptionGroupSet { product in StreamingPassLevel(product) } label: { streamingPassLevel in Text(streamingPassLevel.localizedTitle) } marketingContent: { _ in StreamingPassMarketingContent() } } .subscriptionStoreControlStyle(.compactPicker, placement: .bottomBar) .subscriptionStoreOptionGroupStyle(.links)
-
13:41 - Custom control style implementation
import StoreKit import SwiftUI struct BadgedPickerControlStyle: SubscriptionStoreControlStyle { func makeBody(configuration: Configuration) -> some View { SubscriptionPicker(configuration) { pickerOption in HStack(alignment: .top) { VStack(alignment: .leading) { Text(pickerOption.displayName) .font(title2.bold()) Text(priceDisplay(for: pickerOption)) if pickerOption.isFamilyShareable { FamilyShareableBadge() } Text(pickerOption.description) } Spacer() SelectionIndicator(pickerOption.isSelected) } } confirmation: { option in SubscribeButton(option) } } } struct DestinationVideoShop: View { var body: some View { SubscriptionStoreView(groupID: Self.subscriptionGroupID) { SubscriptionPeriodGroupSet { _ in StreamingPassMarketingContent() } } .subscriptionStoreControlStyle(BadgedPickerControlStyle()) } }
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.