Home
Blog
Authors
Kendall Gelner

Kendall Gelner is a Senior iOS Engineer at Fieldwire. Previous, he was the founding iOS Architect at Nami ML. He is well regarded in the iOS development community for his technical knowledge and platform experience going back to the App Store launch. The last SDK Kendall was responsible for shipped inside of some of the most widely installed apps, reaching more than 200 million devices.

Latest articles by
Kendall Gelner
Written by
Kendall Gelner
22 Mar

How to add in-app purchases in SwiftUI with Store Kit 2: A Comprehensive Guide

Incorporating in-app purchases (IAPs) is a crucial step for monetizing your iOS app. With the introduction of StoreKit 2, Apple has simplified the process, offering powerful new tools and a more straightforward API. This guide will walk you through the steps to add in-app purchases to your SwiftUI app using StoreKit 2, ensuring a seamless experience for both developers and users.

What's SwiftUI?

SwiftUI is a modern framework introduced by Apple for building user interfaces across all Apple platforms using a declarative Swift syntax. Launched in 2019, SwiftUI allows developers to create robust, dynamic, and interactive user interfaces with less code and more efficiency. Instead of manually managing the state of the UI, SwiftUI handles it automatically, allowing developers to focus on defining what the UI should look like for any given state.

SwiftUI works seamlessly with other Apple frameworks and is designed to be integrated with existing UIKit and AppKit projects.

How to add in-app purchases in SwiftUI with Store Kit 2: Step-by-Step

Step 1: Configure Your Project in App Store Connect

1.1 Create an App Record

  1. Log in to App Store Connect.
  2. Go to "My Apps" and click the "+" button to create a new app.
  3. Enter the required information and save.

1.2 Set Up In-App Purchases

  1. Select your app and go to the "Features" tab.
  2. Click on "In-App Purchases" and then the "+" button to add a new in-app purchase.
  3. Choose the type of in-app purchase (consumable, non-consumable, or subscription).
  4. Fill in the necessary details like Reference Name, Product ID, and Pricing.
  5. Submit the in-app purchase for review.

Step 2: Implementing In-App Purchases with StoreKit 2

2.1 Import StoreKit

First, import the StoreKit framework to access the necessary classes and methods for implementing in-app purchases:

import StoreKit


StoreKit is the framework provided by Apple to handle in-app purchases and subscriptions. By importing StoreKit, you gain access to various tools and functions that simplify the process of fetching products, managing transactions, and verifying purchases. StoreKit 2, introduced in iOS 15, enhances this functionality by offering a more modern, Swift-friendly API that leverages Swift concurrency.

2.2 Define Your Products

Create a class to manage in-app purchase products using StoreKit 2:

class IAPManager: ObservableObject {
   @Published var products: [Product] = []
   
   init() {
       Task {
           await self.retrieveProducts()
       }
   }
   
   func retrieveProducts() async {
       do {
           let productIDs = ["com.yourapp.productid1", "com.yourapp.productid2"]
           products = try await Product.products(for: productIDs)
       } catch {
           print("Failed to fetch products: \(error)")
       }
   }
}


This IAPManager class handles the fetching of available products from the App Store. The @Published property wrapper allows SwiftUI views to reactively update whenever the products array changes.

2.3 Purchase Handling

Add methods for purchasing and handling transactions:

extension IAPManager {    func purchase(_ product: Product) async -> Bool {
       do {
           let result = try await product.purchase()
           switch result {
           case .success(let verification):
               let transaction = try self.verifyPurchase(verification)
               await transaction.finish()
               return true
           case .userCancelled, .pending:
               return false
           @unknown default:
               return false
           }
       } catch {
           print("Purchase failed: \(error)")
           return false
       }
   }

   private func verifyPurchase(_ verification: VerificationResult<Transaction>) throws -> Transaction {
       switch verification {
       case .unverified:
           throw NSError(domain: "Verification failed", code: 1, userInfo: nil)
       case .verified(let transaction):
           return transaction
       }
   }
}

The purchase method handles the entire purchasing process, including starting the purchase, verifying the transaction, and finishing it. The verifyPurchase method ensures the transaction is valid and comes from the App Store.

2.4 Integrate with SwiftUI

Use the IAPManager in your SwiftUI views:

struct ContentView: View {
   @StateObject var iapManager = IAPManager()
   
   var body: some View {
       List(iapManager.products, id: \.id) { product in
           VStack(alignment: .leading) {
               Text(product.displayName)
               Text(product.description)
               Text(product.displayPrice)
               Button("Buy") {
                   Task {
                       await iapManager.purchase(product)
                   }
               }
           }
       }
   }
}


This SwiftUI view displays a list of available products, with buttons to initiate the purchase process. The @StateObject property wrapper ensures that the IAPManager instance is created and retained correctly within the view.

Step 3: Testing In-App Purchases

3.1 Create a StoreKit Configuration File

  1. Create a StoreKit configuration file in Xcode.
  2. Add your in-app purchase products to this file.

3.2 Enable StoreKit Configuration in Scheme

  1. Edit your scheme in Xcode.
  2. Under "Run" > "Options", select your StoreKit configuration file.

Step 4: Advanced StoreKit 2 Features

4.1 Handling Subscriptions

To manage subscriptions, you need to handle the different states a subscription can be in, such as active, expired, or in a billing retry period.

func handleSubscriptions() async {
   do {
       let statuses = try await Product.subscriptions.allStatuses()
       for status in statuses {
           switch status.state {
           case .subscribed:
               // User is subscribed
               break
           case .expired:
               // Subscription expired
               break
           case .inBillingRetryPeriod:
               // Billing issue
               break
           default:
               break
           }
       }
   } catch {
       print("Failed to fetch subscription statuses: \(error)")
   }
}


4.2 Restoring Purchases

Users expect to be able to restore purchases, especially if they switch devices. Implement a restore function to handle this.

func restorePurchases() async {
   do {
       try await AppStore.sync()
       try await updateUserPurchases()
   } catch {
       print("Failed to restore purchases: \(error)")
   }
}


4.3 Transaction Listener

Listening for transaction updates ensures you catch any purchases made on other devices or pending purchases.

@MainActor
func listenForTransactions() {
   Task {
       for await verificationResult in Transaction.updates {
           do {
               let transaction = try self.verifyPurchase(verificationResult)
               // Handle the transaction
               await transaction.finish()
           } catch {
               print("Transaction verification failed: \(error)")
           }
       }
   }
}


4.4 Updating User Purchases

Keep track of the user's purchased items to unlock content appropriately.

@MainActorfunc updateUserPurchases() async {
   do {
       for await verificationResult in Transaction.currentEntitlements {
           let transaction = try self.verifyPurchase(verificationResult)
           switch transaction.productType {
           case .consumable:
               // Handle consumable purchase
               break
           case .nonConsumable:
               // Handle non-consumable purchase
               break
           case .autoRenewable:
               // Handle subscription
               break
           default:
               break
           }
       }
   } catch {
       print("Failed to update user purchases: \(error)")
   }
}

Different kind of purchases available

There are different kinds of data you can hold purchase activity in, how to write code to handle purchase activity with StoreKit, or use a framework like Nami which manages StoreKit transactions for you and can work with data stores like the ones described below.

Subscriptions

As subscriptions are a model where a customer gives you money over time in return for some continuing value of your application, it's a good idea to keep in mind the customer journey with your application - are they brand new?  Might they have been using your app for some time and are about to subscribe?  If they have subscribed, are they going to renew, or have they opted to cancel a current subscription and let it just run out?

Fundamentally, the simplest things you would want to track for a subscription are:

Just from those, you can adjust displays in your application to access content, or to provide some additional messaging as the end of a subscription draws near.

One-Time Purchases

This is what most people think of when they talk about In-App Purchases. In this case, the purchase is made only once and lasts forever.  So it's enough to simply track if something is purchased.

Consumable Purchases

Consumable purchases allow a user to make the same purchase more than once.  Two common use cases are coin packs in games and apps that let you buy and spend credits.  For another uses case of consumable purchases, see our guide on creating a tip jar for your app.

Since users can make a purchase more than once, it's a good idea to keep track of how many times they may have purchased, for either messaging around thanking them for each purchase or adapting the application to reward multiple purchases in some way as well as the user’s credit balance:

You can add more details around any of those items, but those are great starting places.

Where to Put Purchase Data?

When you decide what kind of purchase data you want to preserve and react to, you then need to decide how to store it in a way that a SwiftUI application can react to it.  In order to do that, you can make use of the Combine framework, which allows you to have an object that publishes changes.  For SwiftUI, that means making an ObservableObject, with Published properties that correspond to the kinds of things you'll be looking for.

Creating your ObservableObject

An Observable object is made by declaring one or more Published properties, that when changed will notify any views using the properties.  

For this example, we’ll create an ObservableObject for a subscription.

Then you can either add methods to the ObservableObject to alter the Published properties as needed, or modify them externally via some other code.  An example of setting up a listener for an Observable object to change purchase state for the object above would be to add an init like so:

When you process a purchase with StoreKit, check to see that the purchase has completed, and then send a notification that triggers the ObservableObject to update values:

Note that properly validating a purchase requires looking at the receipt which should be done on a server.  Take a look at this blog post to get started.

Correctly updating subscription state over time also requires a server to process Apple’s Server to Server notifications and correctly update a model of the customer’s subscription lifecycle.  This is beyond the scope of this article.  

If you are using the Nami ML platform, we automatically manage the subscription lifecycle for you.  The code sample below provides an example of fully managing a customer’s access to a subscription in a SwiftUI app.

Creating and Accessing ObservableObject

Once you have an ObservableObject, you'll need to be able to use it for a variety of views across your application.  You also want to create that ObservableObject as early as possible, since purchase messages might be triggered as soon as your application launches.

You could simply create an instance of the object and pass it into every view via an initializer, but chances are you will not need to know about purchase status in every view.  Thus, it's easier to add your ObservableObject into the application environment where any view can access it directly without having to have been passed in the object.

The best place to do setup and add the ObservableObject to the environment is in your App class, where your initial Scene is created.

Now in any class that you want to use your ObservableObject properties, you can just add the object from the environment.

Allowing Access to Paid Content and Features

When your users make a purchase, that purchase unlocks an entitlement that grants them access to the paid features of your app.

Now that we have all the basics in place, let’s look at a few different options of how you can grant access to paid features in your app.

View Visibility

You can optionally display a view, based on the current state of purchases.

You can make an even more complex choice, deciding to display a view based not just on purchase state, but on some other variable like subscription expiration.

In this example, there may be a completely different view for a paid subscriber than there is for a free user.

Conditional View Content

In the same way, you can conditionally add whole views, you can also opt to change content based on the purchase state.

This can be a good way to show paid features that exist on a view or enable a disabled button that does not work for a free customer.

Navigation

When you have a button that accesses paid content in your application, you can check if the user has the correct access for that content.

If they do, allow them to see the content, otherwise you can present your paywall with your purchase options.

This is a general pattern you may use to protect paid content in your app.

Integrating Full Purchase Support

A full StoreKit implementation is beyond the scope of this article and requires some updates to your app code as well as some server-side components as well.  This talk has a good overview of the basics.

The Nami platform also takes care of the complexity of StoreKit integration so you can focus on other aspects of your app.  Check out our SwiftUI page or Quickstart Guide for an overview.

SwiftUI makes it very easy to modify UI in reaction to purchases via waiting for state changes.  You should think about adding support for purchases as early as possible during the design of your application, so the integration of purchase boundaries feels as natural as possible and doesn't cause you extra work rearranging UI to support purchases later.

Written by
Kendall Gelner
5 Nov

Maximize iOS Testing Coverage: Unit Test vs. UI Test

Learn the difference between an iOS Unit Test and UI Test and when to use each. Discover how to move a Unit Test into a UI Test plus some helpful tips to deal how unit tests and UI Tests are run.

In this article, we'll talk about the differences between an iOS Unit Test and UI Test. We’ll also cover when you might want to choose one type of test over the other.

We'll also give some advice on how to move a iOS Unit Test into a UI Test. Also, we’ll look at when each is appropriate to use. Finally, some helpful tips for Unit Test and UI Test runs.

Xcode iOS UI Test and Unit Test dialogue

Two Types of iOS Tests

For those new we'll define what are the fundamentals of these two different kinds of tests. In both an iOS Unit Test and UI Test the test class inherits from XCTestCase. Then, the tests run in a simulator instance. So, what is the difference between the two kinds of tests?

iOS UI Test

A UI Test focused on manipulating UI elements. This is so you can test that UI interactions cause the correct behavior. UI Tests can also inspect values of UI elements to see if an app runs as expected. 

You can manually create UI Tests. Also, you can create them by recording a series of .UI actions. This sequence is repeatable via a test. You can also add further checks within the test to verify application state.

However, UI Tests can only manipulate and inspect UI elements. The test classes themselves are running in a separate process from your application. They have no access to the internal app state. They can only inspect what appears in the UI (via accessibility features of iOS).

UI Tests also have an important aspect. Between tests, the application launches separately.  Also, you are able to suspend and resume an application in a UI Test. This is because the test has access to an app instance to manipulate.

iOS Unit Test

A Unit Test is a test that runs within the same process as your app. This means it is free to call internal methods of the app or SDK you are testing (via an @testable import statement). Thus, you can inspect state while running, or activate specific methods for testing.

However, an iOS Unit Test cannot access anything related to the UI on screen. A Unit Test also cannot access the running XCUIApplication instance in order to do things like suspend or re-launch the application.

Also, each testing run spawns one testing application instance used for all Unit Tests.  Importantly, Unit Tests run sequentially when housed in separate files. Unit Tests may run in parallel when housed in a single file.

This diagram summarizes the difference between the two kinds of tests:

Diagram of XCTest and XCUITest interaction with SDK instance

How to Choose: Unit Test or UI Test

The choice to make aUI Test or Unit Test depends on what you are trying to test. If you want to verify screen content or interact with UI you should use a UI Test. If you want to inspect application or SDK state or call internal methods, you will generally start with a Unit Test.

However you may come to a point where you run into the limitation of either type of test.

In the middle of a UI Test you might want to verify some internal application state.

For an iOS Unit Test, other running tests can alter internal state. This can cause tests to fail that might not otherwise when run by itself.

In these cases, try to start up the application in a clean state where no other tests have run, even if other tests have run previously.

Sometimes that means you want to be able to re-launch the application in a certain way while still manipulating state inside.  So how can we work with the differences between the two test types?

Combine iOS Unit Test & UI Test Features

Given we may want to use features of both UI and Unit testing in some tests, what can we do?

A flexible approach involves combining both kinds of tests. Use a UI Test that accesses screen elements that trigger internal methods in your application. Then, populates text fields with results or state of your application/SDK.

As these UI elements exist purely for testing, you can have a hidden testing screen within an app that a UI Test can trigger. Alternatively, you can create a separate testing application for something like an internal SDK that holds UI elements just for testing.

The general approach to building a test that takes this approach is:

  1. Build a UI control (generally a button) for the testing screen. When tapped, run a small block of code to do the test you desire. Based on the result of running that test code, alter a text field or label on the UI with results.
  2. Set up an expectation for the test at completion (either complete or failed).
  3. Introduce a small delay to make sure the application is fully initialized.
  4. Find the UI element (generally a button) on your test screen that corresponds to starting your desired test code.
  5. Trigger the UI element with a .tap() call.
  6. After a delay long enough for the test code to complete, examine the UI control that will hold results of your test run (such as a label). Example results from the test.
  7. Get the value of the UIElement and test for expected results, using XCTAssert to check conditions for failure.
  8. Call expectation.fulfill() to indicate the test is over.

A Real-World Test

Let's see those steps in action within a real test:

The test relies on getting access to specific UI elements. A button to activate a purchase and a label where test results are stored.

The button itself is obtained by using the text from the button. For the label, we have to add a bit of additional code in the main application view controller that displays the purchase button and results label:

The XCUITest cases use Accessibility to get at ID values to recognize the label. An accessibility value is set to the results of the test so set it (and the label text) to an initial value that indicates the test has not yet completed.

The code for the button press is kept simple to do just one task. It could be more complex:

Clean Slate Testing

You may need to clear out application state to run a particular test. There are a few options:

For an iOS Unit Test, you can implement a method accessible via the @testable import that class out pre-existing state that may be set from other tests within the application.

For UI Tests, you can create a command line parameter that you can pass to a test on launch. This would be used clear out state from previous launches. It would also access a method similar to the one described for cleaning application state.

At times however, you may need to be sure a iOS Unit Test is run after a clean launch within other tests run.

In that case, you can move a Unit Test into a UI Test class. Putting the code you wish to test into the application, triggering it via a UI element, and putting the results into a label for the test case to verify as described previously.

For a fully clean start you can pass in a launch argument from your test case:

Note that app.launch() re-launches the application after the initial test being activated runs it.

The code in the application to look for arguments being passed and act on them looks like:

Final Thoughts

Testing is always a balance between deciding what things are important to test for, and what is possible with your tools. Techniques like these should expand what is possible to test. Plus, gain greater flexibility in testing your app to the fullest extent possible.

Written by
Kendall Gelner
11 Jun

What’s New with In-App Purchases in iOS 15 - WWDC 21

iOS 15 introduces tons of new subscription and IAP capabilties including refunds, customer support, StoreKit 2, new server APIs and server notifications, and more.

The introduction of iOS 15 is bringing many new updates to in-app purchases and subscriptions, including StoreKit 2, Server Notifications v2, new server-side APIs, customer refunds, signed transactions, in-app events, new customer support capabilities, and new sandbox testing options. It was a huge week at Apple’s WWDC 21 for subscription and in-app purchase news. Tons of updates are coming this year to help make it even easier to sell subscriptions and IAPs in your apps. Below we’ll look at the highlights of the various announcements, how they might impact your apps, and how Nami works with all the latest and greatest tools from Apple.

Refunds

Let’s start with refunds as this was the single biggest announcement that offers new capabilities to app publishers to help create and maintain happy customers.

Apple had 2 big announcements here:

  • You will be able to start a refund process directly in your app
  • You’ll be able to start a refund process with a new server-side API

Starting a refund request in your app is very simple, there’s a new StoreKit method beginRefundRequest(). This will launch an Apple-controlled UI that collects a reason for the refund request.

Note that the request still goes to Apple for review and then they make a decision and let the customer know, the refund is not instantaneous.

Another new interesting feature is Apple has already started sending a new CONSUMPTION_REQUEST server notification. This is a request for information about whether the customer has made use of their consumable IAP to help in making a decision on a refund request. For apps that sell consumable purchases, this will be a very important request to respond to.

To learn more about refunds, check out Apple’s full session.

Customer Support

Customer support on the App Store has always been tricky because Apple did not have any tools to help app developers look up a particular customer record or transaction. They’ve now introduced a new server-side API that will enable building tools to help you look up customer records. It is all triggered from the Invoice ID that the customer receives via email for each purchase (and each renewal of an auto-renewable subscription).

The only challenge here is that this ID is only available to the customer and not to the app developer, so you can’t pre-build and store this type of information in advance. It will only be provided by the customer when they reach out to your customer service.

Signed Transactions

There are a number of updates included in what Apple is calling Signed Transactions, which are an update to the existing Apple receipt. We won’t get into the technical details of working with Signed Transactions in this article but look for some coverage from us soon to help you understand how to work with these new elements. These new transactions are signed using JSON Web Signature (JWS) and you’ll have to decode them to validate and check the data contained within.

Perhaps the single most important part of Signed Transactions is you can now directly verify a purchase in your app on-device without having a server that talks to Apple to verify the receipt. This requirement was often a barrier to app developers and is a big simplification for fighting against fraud in your app.

A couple of other interesting notes:

  • Data elements are now actually integers and booleans instead of everything being a string, which should make them easier to work with
  • Data payload is now a JSON instead of the custom receipt format, making parsing much simpler
  • It seems Apple has gotten rid of the odd time format used in the receipt payload that looks like UTC but is actually something different. Also, there were some timestamps that were in different time zones as well as different formats. You can find full details on what this looks like in receipt payloads here if you happen to be curious.

StoreKit 2

iOS 15 is bringing big changes to Swift in general and the StoreKit framework in particular. Most notably is the introduction of Swift Concurrency which is heavily used in the new StoreKit 2 framework.

In addition to simplifying how you work with receipts using the new Signed Transactions discussed in the last section, there are a few new capabilities that are coming with StoreKit 2

  • Easier access to products
  • Simplifying how to make a purchase
  • New ways to handle app accounts
  • On-device purchase validation

Let’s look at a few simple examples here. In the new StoreKit you no longer have to work through SKProductRequest. Getting a product is as simple as:

To then purchase that product, you can make the following call:

Finally, to verify the purchase on-device you can:

Pretty simple! There’s a lot more you can do with StoreKit 2, but this simple set of a couple of examples really highlights the power and simplicity o the new APIs.

The other big capability we mentioned above is to help you if your app has accounts. You can now create an App Account Token that is a UUID that Apple will tie to the purchase for you.

Simply add this App Account Token to the purchase and it will show up in any method where you access purchases as well as in the Signed Transactions. This greatly simplifies how you manage access to purchases when you have an app account. In StoreKit 1, all this logic has to be custom-built on your server.

Watch Apple’s Meet StoreKit 2 session to learn more.

New Server-Side APIs

Apple announced a number of new server-side APIs. Here’s a full list:

Consumption - provide data on consumable IAP usage to help Apple make a decision on a refund request

In-App Purchase History - Get a list of Signed Transactions for all purchase the user has made

Invoice Lookup - takes the order ID from their emailed invoice and returns if the ID is valid and any associated Signed Transactions

Refunded Purchases - takes the original transaction ID and returns a list of Signed Transactions for any purchases that have been refunded

Renewal Extension - allows you to extend the current bill term of a subscription by up to 90 days. Used to issue refunds or credits on subscriptions

Subscription Status - Check the status of a subscription at any time with the original transaction ID

Server Notifications v2

There was a lot of news here if you maintain the server-side portion of Apple’s in-app purchase system for your app. There were 2 big pieces of news here:

  • Apple is adding both new events and a new field called substate.  The combination of a single server notification and its substate is now supposed to have a 1:1 mapping to an actual customer lifecycle event.
  • The notification payload will include the Signed Transaction so that a separate call to Apple is not needed to interpret the notification.

As many of you will know, the existing Server Notifications from Apple do not have a 1:1 mapping to subscription lifecycle events. In some cases, multiple events are sent that have to be interpreted in combination to understand what has happened. Additionally, you always have to make an additional API call to Apple to get the latest receipt which is required to determine the lifecycle state of the customer.

List of Server Notifications types in available in version 1 and version 2.


In addition to these big changes, a few other items were announced:

  • As mentioned above, a new CONSUMPTION_REQUEST notification that requires a response be sent back to Apple’s servers has been introduced as part of the new refund process
  • New Server notifications will also be sent for Family Subscriptions

The new family notifications will be sent in both v1 and v2 versions of Server notifications. Different notifications will be available in each version.

Learn more in Apple’s Manage in-app purchases on your server WWDC 21 session.

Sandbox Testing

Apple announced new capabilities in sandbox testing, including the ability to:

  • Clear purchase history
  • Change sandbox account region
  • Adjust subscription renewal rate

You will be able to control all of this from App Store Connect.

This is a huge improvement that will enable easier testing on a few key items:

  • Localization of your purchase experience in different markets
  • Free Trials and introductory pricing
  • Faster testing by accelerating the time to renewal

Look for more recommendations from us on improved testing patterns for your in-app purchases in the future.

Use Case: IAPs and In-App Events

In a very exciting announcement from Apple, they now support In-App Events. In-app events will be promoted and searchable on the App Store. Events can require specific in-app purchases or subscriptions to access and may contain a deep link to take you directly to the specific event page in your app.

If you plan to create in-app events for your app, you can use Nami Paywalls to help gate access to your in-app events. Simply create an Entitlement that grants access to the event and when deep linking to the screen for the event, check the entitlement status with the Nami SDK. If the user does not have the correct entitlement, display a paywall created with our cloud Paywall Creator to ask them to make the purchase to grant access.

This is a great new feature to help create value in your app for your paying customers. We are looking forward to seeing how different apps use this in the future.

What changes for Nami?

The great news here is that most of these updates only require some internal changes to our platform, and do not affect how you as a developer use Nami.

Nami also helps to solve the challenge of leveraging iOS 15 only features in apps that still have a large install base on iOS 14 as we’ll be supporting both frameworks in our SDK.

We are excited about the new IAP use cases around in-app events that you’ll be able to enable with Nami and will be working to seamlessly integrate the new refund capabilities with our existing Customer Support tools.

Excited about testing out StoreKit 2 code yourself? Did you know Nami can run along-side a custom-written StoreKit implementation. You can still leverage all the great tools on the Nami platform and use your own StoreKit code. Reach out to us to learn more.

Written by
Kendall Gelner
18 Sep

In-App Purchases and StoreKit in iOS 14 and Xcode 12

The Xcode simulators for iOS 14 work differently than iOS 13 and earlier. We step you through how to get IAP purchase testing working in the iOS 14 simulators.

Apple just released Xcode 12 and iOS 14.  This release includes great new features for in-app purchases which we’ve discussed here.  With iOS 14 now live for your customers, there are some very important changes to how you need to test and develop your app with StoreKit and IAPs.  In this article, we provide an overview of important changes to testing purchases in the iOS 14 simulators.

If you've installed Xcode 12 and run anything using StoreKit in an iOS 14 simulator, you may have noticed a problem - StoreKit products you set up on AppStoreConnect no longer load, so any purchase pages you set up may be blank!

App running in iOS 13.2 simulator from Xcode 12. Products still appear.

The same app but running in the iOS 14 Simulator. No products!

Now, iOS 13 simulators will still work as you are used to, as will sandbox testing on device (with some enhancements which we'll get into in future articles). You'll likely want to continue testing on iOS 14 simulators as well, so how can we get our products back?

The answer: migrate our existing App Store Connect product definitions to a StoreKit configuration file for Xcode12

There you can see a list of the products defined in your application. Find the ones you want to use for testing, then click on one to load product details:

Open your application project in Xcode 12 and create a new StoreKit Configuration:

You can choose whatever name you like and save it into your project; if desired you could even have multiple StoreKit Configuration files. Do not add it to the project on creation; it actually gets set up in the application Scheme as we'll see shortly.

The configuration .storekit file will be added to your project navigation, where you can then select it to edit:

When you have the configuration open for editing, use the "+" button to add the product you previously had selected in App Store Connect:

At this step, choose the type of product that you have set up in App Store Connect, either consumable, non-consumable, or auto-renewable subscription in-app purchase. In this example case I'll add an auto-renewable subscription product.

As a first step because I've not added a subscription product before, it will ask for a subscription group - it's a good practice to name this group the same as you have in your App Store Connect product:

Now we have a new product with a temporary name, ID, and price.

The most important thing to change is to alter the product ID to match what you have in AppStoreConnect since your code will be expecting a product with a specific product ID, as I've done with this example product:

You can enter as many products as you like.

Now that we have a StoreKit configuration file, how can we make use of it? In the top menu bar target dropdown, select "Edit Scheme":

When you select Edit Scheme, you'll be able to select the configuration file you just created for the "StoreKit Configuration" entry:

When you activate this configuration, it will stay enabled for any change in simulator or device you select to run on. Note that it will not have any effect on pre-iOS 14 simulators or devices, it needs iOS 14 to take effect.

Also it is worth noting that after adding this Configuration to the scheme, the project will still load and work in older versions of Xcode, but they may revert or clear the chosen StoreKit configuration.

Now when I run on an iOS 14 simulator I see the product I added on my payment screen:

Note that in my case my payment screen is showing only the product I entered correctly, that's also the only one that would work for any purchase attempts that used StoreKit to purchase. The others are products the paywall knows should be there, but provides only placeholder values for since it can find only one of the three products desired in the StoreKit configuration.

To keep testing simple you may want to keep the number of products defined in a StoreKit configuration minimal so it's easier to track with App Store Connect changes, or you can exactly duplicate your full product set.

In the case of subscription testing in the simulator, because you can select custom time acceleration you could use any subscription duration and have it expire quickly if you wished - that and more will be talked about in more detail in a future article.

Note that this StoreKit configuration you've enabled in the Scheme will also be used on an iOS 14 device, and you'll not be using a sandbox account. If you want to run a device build and still use a Sandbox account for testing, simply deselect the StoreKit Configuration in the application Scheme.

Written by
Kendall Gelner
30 Jun

What’s New with In-App Purchases? WWDC20 Updates

Apple announced a lot of changes to subscriptions and IAP in iOS 14 at WWDC this year. Our summary has you covered on the most important changes.

Apple’s WWDC20 conference this year contained many updates about in-app purchases, subscriptions, and the StoreKit framework.  In case you did not catch it all or have time to watch all the videos, we’ve got a short recap of the most important updates you’ll want to be aware of.

The major updates to in-app purchases fall into three main categories

Let’s take a look at each one of these in a little more detail.

New IAP Features for Customers

Family Sharing

The ability to create a subscription that can be shared by a family is one of the most exciting announcements this year.  Now you will have the option to create products at different price points for a family shareable subscription or a subscription for an individual consumer.

To use this feature, you first must enable family sharing in App Store Connect for a particular subscription.  By turning this feature on, all members of the same family set up in iCloud can share a single purchase to a subscription.  This is a great new benefit you can offer your customers with a little bit of configuration on your app.

Once you turn on family sharing for a subscription, you cannot disable it.

The consumer purchasing the subscription must also agree to enable subscription sharing for family members.  The person making the purchase can also choose to shut this off.  Once a subscription with family sharing has been purchased, other members of the family can access the subscription by doing a Restore Purchase action on their device.

How does this work in StoreKit?

StoreKit will see each family member’s subscription as a unique transaction as if they had purchased the subscription themselves.

SKProduct now also adds an "isFamilySharable" flag to a product, so you can tell which products are available to share with other family members and present that information to the user.

There is also a new API on the SKPaymentQueue to help manage when a user decides to disable family sharing called didRevokeEntitlementForProductIdentifiers.  In this method, you’ll need to re-verify the receipt and revoke access as needed.

More detailed information on App installs from Ads

Apple announced some exciting improvements to how you can track attribution of any ad campaigns you are running for your app.  The highlights include

  • Setting custom Conversion Values
  • Tracking of re-installs
  • Data on which apps your adds successfully converted from

Setting custom conversion values is a very powerful new feature.  Now instead of just tracking installs, you can track whether users are engaging with specific features in your app, registering for an account, or even making an in-app purchase.

SKAdNetwork now has an updateConversionValue that takes an integer.  On subsequent calls, Apple will update this value only if the new integer is greater than the old one.  This means it is very important to structure your conversion values along a path of increasing engagement in your app, to get the most insight.

Apple used to only track first-time downloads via SKAdNetwork, but now there is a new field Redownload.  You can use this to see if your ad was successful in getting a user to reinstall your app.

Finally, Apple has added a Source App ID, so you can track if certain apps are better at acquiring you downloads.  

In your SKAdNetwork postback, you’ll now receive Redownload, and optional values for Source App ID and Conversion Value.  These last two are optional because Apple will only include them if it can do so without risking identifying a specific customer.

In-app cross-promotion of your entire app catalog

If you have a catalog of multiple apps, Apple announced some great new features for you.  There’s a new API called SKOverlay that allows you to present an overlay at the bottom of your app’s UI that customers can directly install an app from.

Two main uses for this new UI element are

  • If you are using Apple’s new App Clips, promote your full app from the App Clip
  • Promote other apps in your catalog to your customers

StoreKit basics

  • Initialize with a SKOverlay.Configuration object
  • Call present method with UIWindowScene to show the UI element
  • There’s a dismiss call to manually dismiss the overlay
  • Use the SKOverlayDelegate to react to changes in the overlay status

For more details on each of these customer facing updates, watch the second half of this video:

https://developer.apple.com/wwdc20/10661

Updates to Server to Server Notifications

There were two new Server to Server notifications announced this year

  • The REFUND event will be sent when a customer is issued a refund
  • The DID_RENEW notification will be sent for every single renewal

REFUND is the first ever server notification for content types other than auto-renewable subscriptions.

With the introduction of the REFUND notification, Apple is broadening the scope of this service to include all purchase types. We expect more server-side data for all IAP types to be added over time, so it is important to make sure you are handling server-side events from Apple in your IAP process.

In StoreKit, there's a new listener callback to detect refunds, or subscriptions being revoked, didrevokeEntitlemnetsForProductIdentifiers.

The new DID_RENEW event is going to be a great addition that gives a more clear signal to track what is happening with your users.

Apple continues to move step-by-step in the direction of their Server to Server notifications being the primary way for an app to understand the customer lifecycle and payment activity. When receiving a Server to Server notification, server-side verification of the receipt is still the best way to then validate the status of your customers.

For more details on the latest changes for server to server notifications, watch the first half of this video:

https://developer.apple.com/wwdc20/10661

StoreKit Testing

Apple announced a whole set of tools for testing your in-app purchases in Xcode.  These new capabilities will be a huge help in testing and adding IAP to your app.

Simulator Improvements

It is now possible to use and test StoreKit in the simulator.  This will allow you to

  • Define products locally in a StoreKit configuration and choose which product configuration to use via the scheme editor.
  • Simulate a variety of exceptional purchase conditions, like Ask To Buy or payment issues.
  • Alter the duration of test subscriptions so even a year can expire quickly to test the handling of expiration.
  • Use the Xcode browser to examine and modify the status of subscriptions in the simulator.

Unit Testing

Apple introduced StoreKitTesting, a testing framework to enable you to test various aspects of purchase flows for both unit and UI tests.

On-Device Improvements

Apple has announced a number of on-device improvements to working with the Sandbox.

  • Sandbox test account management has been improved.
  • Examine and modify the status of subscriptions for a test account.
  • Reset aspects like trial and introductory offer eligibility for an account.
  • Ability to place test accounts into states to test purchase flow errors in App Store Connect.

All of these new StoreKit testing options open up a bunch of new ways to be confident that your purchase process is working correctly and reliably.

For more details on StoreKit Testing, watch this video:

https://developer.apple.com/videos/play/wwdc2020/10659/

Nami’s platform offers tools to simplify providing great purchase experiences to your app users, handle all the server-side events including receipt verification and Server to Server notifications, as well as tools to help you test the purchase lifecycle in your app.  Sign up for free to get started.

Written by
Kendall Gelner
9 Apr

What Every iOS Developer Should Know About App Submission

What every iOS developer should know before submitting an app to the App Store for review.

Its time, development is complete, bugs squashed, and code is clean. Approvals are all done, and you’re now good to go on submitting your app to the Apple App Store. After working on your app for days, weeks or even months…. there is light at the end of that tunnel!

Let’s get started with a few tips on some key things you will need to know in advance:

Sign Into Your Developer Program

If you haven’t already, you will need to register and handover about $99/yr into the Apple Developer Program. It’s worth it as you will get access to a slate of great benefits:

  • Ability to submit apps to all platforms in the App Store
  • Create Safari extensions and post them to the extensions gallery
  • Accessing Apple software Beta versions
  • TestFlight – one of Apple’s best testing tools.
  • Advanced app capabilities and analytics

App Store Connect

This is a key website that you will be able to gain access to upon signing up. App Store Connect is a portal used to manage everything to do with the App Store.

Source: help.apple.com

So now that you’ve got yourself registered, there’s still a couple of things to check before your next step.

  • Your app will need to pass the app certification process. All this is, is a human that will test your app out and make sure it meets all App Store Guidelines
  • Before you submit, it is very important that you review and comply with the guidelines simply because the person reviewing your app will be using this document to judge your app.
  • In keeping with testing your app as much as possible, make sure it is crash-free and bug-free. Put it this way, if the reviewer can crash it, it won't pass. Test it out way beyond normal conditions. Do it using low connectivity, and/or low storage conditions as well as testing it on older devices for example. Basically, do your best to break it!
  • This is a good time to make use of friends and Apple’s TestFlight beta tester program.

Create your App Store Listing

This will be done via your App Store Connect portal. Once you are in, select the My Apps menu and then the “+” option. As you complete the information, you can click the “?” button if you have any questions about a specific field. At this stage, you would be filling out the name, description, category, minimum audience age, support URL and your privacy policy.

If you’ve created an app that involves social media and requires a login, you’d need to provide one for the Apple reviewer to access the app’s social features. Additionally, ensure your privacy policy is submitted as a live document online with a public URL.

This is also the stage where you’d be submitting your pricing. You will have a few options here including a scheduled date of delivery as well as allowing for pre-orders and all territories where it will be available.

Screenshots of Your App

Not too much to include here except to add that you must make time to read about submitting proper screenshots required by the App Store. This link will take you right to the screenshot specifications. This is especially critical if you are using multiple sized displays (iPad, Apple Watch, etc.)

Making screenshots can be done, for example, in a couple of ways:

  1. You can use raw screenshots. This is the fastest and easiest way by launching your app in the iOS simulator. Be sure to set it for the screen size that you need. Find the screenshot you would like to use then hit CMD+S to take the pic. Simply upload the PNG file to the App Store Connect.
  2. You can also use a mock-up template.

If you decide to not to rely on actual screenshots, you'll need a couple of tools.

First is graphics software to help with your screenshots, you can try any of these: Pixelmator, Figma, Sketch, Adobe Photoshop. TIP: Figma is free by the way and works great!

Next, you’re going to need some device mockups.

Mockuphone is free to use. Simply select your device, upload the screenshot of choice and DL the mockup. You can choose between portrait and landscape options. Import it then into your graphics app and add any text needed.

Rotato is another one that you can use. It’s a great paid app that uses a “camera” under your control on a 3D mockup. This gives you awesome angles to work with. Again, once you’ve got your shot, upload it to your graphics app.

Angle is a paid collection of mockups. They are in a variety of angles and device frames preset for you to import into your graphics app. FYI: If you pick Figma, just know that only the Sketch files will import from Angle, not the AdobeXD files.

You can use an app store screenshot generator if you don’t want to bother using mockups in graphics software. There are a couple tools that will do it for you.

With both Davinci Apps and Shotbot, you only need to select a template, upload your screenshots and add custom text.

Most importantly, be sure to show your app in action. Please do not make the rookie mistake of including a screenshot of your login screen.

Using Xcode to Upload Your App

First things first, ensure you’ve got no red warnings and your app is clean from errors. Then make sure your Signing and Team info is set up in Xcode. Your app version at this point should be 1.0.0 if it’s your first time publishing this app.

Publishing is much easier now as Xcode is smarter now by enabling or creating the certificates, profiles etc. on your behalf as you go along.  Be sure to create the full archive by selecting the Generic iOS Device from your list of simulators. FYI: When you bundle your app in the Archive, it can be a fairly lengthy process as the size of your app will determine how long it will take.

Once you’ve got your perfect archive set, you can choose Distribute App and expect to be prompted to choose the best method of distribution. After you’ve selected iOS App Store, a couple checks to ensure the app being uploaded isn’t broken will take place. FYI: This will also take time depending on the size of your app.

If your upload is good, you will see a successful upload notification next! Congratulations! If you need a bit more help don’t forget your resources at App Store Connect to assist.

It’s Time to Submit Your App for Review

This is where you will add the build that was exported from Xcode. App Store Connect will need a bit of time to process it. You will receive a notification once your archive has been processed and is ready for use.

Once you’ve clicked the save and submit-for-review buttons, you will need to answer a couple questions regarding compliance and advertising. The review process will take about 24 to 72hrs (on average). If approved, you will get an email immediately.

Nami’s mission is to help app publishers grow their revenue using in-app subscriptions. We want to help app developers build their businesses in a way that benefits the company and the customer. Schedule a demo today to start growing your app business.