Streaming is available in most browsers,
and in the Developer app.
-
What's new in StoreKit testing
Discover the latest tools to help you test your in-app purchases and subscriptions. We'll show you how to bring your products from App Store Connect into StoreKit Testing in Xcode, learn about improvements to the transaction manager, and explore your in-app purchase flow in Xcode Previews. We'll also take you through best practices when setting up an Apple ID for the sandbox environment, and show you how to create tests for refund requests, price increase consent, billing retry, and much more.
Resources
- Auto-renewable subscriptions overview
- Handling Subscriptions Billing
- Implementing offer codes in your app
- Learn more about setting up offer codes
- Reducing Involuntary Subscriber Churn
- Setting up StoreKit Testing in Xcode
- Testing In-App Purchases with sandbox
Related Videos
WWDC23
- Explore testing in-app purchases
- Meet StoreKit for SwiftUI
- What’s new in StoreKit 2 and StoreKit Testing in Xcode
WWDC22
WWDC21
WWDC20
-
Download
♪ ♪ Greg: Hi, I'm Greg. Welcome to what's new in StoreKit Testing. In this session, Peter and I will highlight some great new features available for testing in-app purchases in StoreKit. We'll begin by taking a look at some of the ways you can use Xcode 14 to streamline your in-app purchase testing. Next, we'll take a look at some brand-new capabilities you can take advantage of to cover even more corner cases in your in-app subscription implementations. And finally, Peter will show new enhancements to the sandbox testing environment. We'll be working with Food Truck, an app to provide powerful capabilities to food truck operators who sell donuts. I already integrated with StoreKit to offer the full version of the Food Truck sales history feature, and a subscription for an enhanced version of the Social Feed service. Throughout the session we'll use StoreKit Testing in Xcode to test the in-app purchase functionality of our app. Back at WWDC 2020, we introduced StoreKit Testing in Xcode, allowing you to start testing your in-app purchases directly in Xcode. This year, with Xcode 14, we're excited to share some updates to the testing life cycle of a StoreKit app. Just like before, you can create a StoreKit Configuration File in Xcode, and start testing your in-app purchase implementation without setting up an app in App Store Connect. When you're ready to configure your app in App Store Connect, we're introducing a brand-new feature in Xcode 14 to allow you to use the same in-app purchase products that you've entered in App Store Connect with StoreKit Testing in Xcode. If you already have an app on the store, you can start using StoreKit Testing in Xcode right now, without needing to set up a StoreKit Configuration File from scratch. This convenient feature enables you to configure your in-app purchases once, and use the same configuration locally in Xcode, inside your unit tests, in the Sandbox environment, and when you're ready for release, on the App Store. It's easy to sync your products in App Store Connect with Xcode. First, you configure your products in App Store Connect, like this Social Feed+ subscription, for example. Then, you create a synced configuration file in Xcode which will load the product data into Xcode. If you want to make a change, for example updating the US English title, you can make the change in App Store Connect and sync your Configuration in Xcode again. You can also convert a configuration you've synced into a local, editable file to make on the fly changes. Converting a synced configuration to a local configuration is a one-way operation, to sync again you'll need to create a new configuration file. I've already gotten started by setting up a subscription group for Social Feed+, an enhanced version of the Social Feed service the Food Truck app offers. Let's jump into Xcode and take a look at how to use these products with StoreKit Testing in Xcode. I have the Food Truck project open on my Mac. To get started we'll create a new StoreKit Configuration File by going to the File Menu, making a new file, filtering by StoreKit, and clicking next.
In Xcode 14, when we create a new configuration file, we get this checkbox to enable keeping the file in sync with an app in App Store Connect. To create a local file, fill in a name and leave the box unchecked. To set up syncing, we just have to check the box, and confirm that the correct team and app are selected. If desired, we could choose a different team and app using the picker menus. We click next, and choose somewhere to save our file. As soon as we save the file, the in-app purchase metadata begins syncing from App Store Connect. While the data is downloading, we can keep working on our app and keep track of its progress in the Activity Bar. When the sync is finished, you'll notice this file looks different than a typical StoreKit Configuration File. That's because the synced file is in a read-only state. We can see all the data in Xcode at a glance, but to make changes we have to open App Store Connect.
I have the Social Feed+ monthly product up in Safari. Let's update the English title for this product by adding a suffix to help differentiate the product from the yearly plan.
Now that this is updated, let's save and go back to Xcode.
To have this change reflect in our configuration file, we just have to press this sync button in the bottom left corner.
Once the sync is complete, we can see the change reflected in Xcode.
Even though the synced file is read-only, we can still copy over data to a local file to make quick changes inside of Xcode.
In addition to copying items from the configuration file, we can also convert an entire synced file to a local, editable file. All we need to do is open our synced file, go to the editor menu, and click on "Convert to Local StoreKit Configuration".
Keep in mind, you can't undo this operation after converting the file. To sync with the app again, you'll need to create a new StoreKit Configuration File. I want to keep this file in sync with App Store Connect, so let's cancel out of this alert. Now that we have our file synced, let's configure our testing environment. To get started, we'll open the scheme editor.
Select the run action, and select options.
In the options, we can switch between different StoreKit environments from the picker menu. If we choose "None" we'll connect to Sandbox, and if we choose "Food Truck" we'll connect to the Xcode environment. It's that easy to switch between environments depending on our current testing needs, and both environments will now be using the exact same product and subscription metadata. Let's pick our synced configuration file for now.
We've now set up StoreKit in Xcode, so let's get to testing. Since we're using a SwiftUI app, we can preview our subscription store right in Xcode.
Starting in Xcode 14, products from StoreKit configuration files will load right into SwiftUI previews. This makes it super easy to build and test great looking store user interfaces, using real in-app purchase data. Let's try to add some detail to the product options by including a subtitle for our products. We'll just add a Text view containing the product's localized description.
And watch the preview update immediately with the description we set up in App Store Connect. I think this is looking a lot better now.
Now that our UI is in good shape, let's run the app on an iPhone and start some functional testing.
In Xcode 14, there are some powerful new tools in the StoreKit Transaction Manager. With our app running, we can open the transaction manager by pressing the purchases icon in the debug bar.
On the right there's a new transaction inspector that allows us to visualize all of the under the hood details about a transaction. This tool can be useful to understand the state of an in-app transaction. For example, we can see the date this subscription to Social Feed+ expired, and information about its upcoming renewals. We can also jump to the configuration file for a product, subscription group, or subscription offer. We just have to click the jump button next to this subscription group.
And we're brought straight to Social Feed+ in our configuration file.
This inspector will help us out later in the session as we look at more advanced test cases.
We can also filter our transactions now, which is really useful for navigating the list of transactions with all of these Social Feed+ renewals. In our app you'll notice we have access to the annual sales history feature.
We have all of these subscription renewals, which makes it difficult to tell which transaction entitled us to the feature. We can easily find the transaction for the product by beginning to type out its ID...
And selecting the product ID filter from the auto-complete menu.
We can also filter by purchase date, so we can focus on just the purchases we're making now.
Since our subscription to Social Feed+ is expired, let's go into the app and subscribe again.
Now that we've confirmed the subscription, we can see just the new transaction appear.
We just looked at some ways to enhance your in-app purchase testing in Xcode, by syncing products and subscriptions from App Store Connect, using your StoreKit Configurations with SwiftUI Previews, and taking advantage of the new tools in the Transaction Manager. Now, we're going to continue testing Food Truck's in-app purchase functionality by using some new capabilities in Xcode to cover advanced subscription cases. First, we'll look at testing refund requests, allowing people to request refunds for their purchases in Food Truck. Next, we'll test offer codes, to offer promotions to Social Feed+ subscribers, then we'll look at handling price increases in Food Truck's user interface, and last, reducing Social Feed+ involuntary churn by supporting billing retry and grace period. To begin testing refund requests, we'll navigate to this support view in our app, which allows us to choose a recent transaction to refund. The code for this is simple. I just added a refundRequestSheet view modifier to our view, and when we press the refund button, we'll flip the isPresented Binding to true. Now, let's see this in action.
When the Binding is true, the refund request sheet appears above our view. When testing in the Xcode environment, the issue we select corresponds 1:1 with a RevocationReason in the StoreKit API. Let's pick "Developer Issue" and press "Request Refund".
In the App Store, refund requests will take some time to process, but when testing with Xcode or Sandbox, refund requests will immediately refund the transaction. In the transaction manager, we can look at the inspector for this updated transaction to see the revocation reason we just selected, and the revocation date.
You can also test refunds by just clicking the refund button in the transaction manager. The refund request API helps us provide great customer support for people who use Food Truck. Now that we looked at how to test refund requests in Xcode, let's look at some ways you can use StoreKit to handle refunded transactions.
After refunding a transaction, an updated Transaction value will emit from the Transaction.updates sequence. We can use the revocationDate and revocationReason properties to detect these refunded transactions. It's easy to test the two revocation reason cases by selecting the corresponding option in the refund request sheet in Xcode. That's how you test the refund request sheet in Xcode. This works on iOS and macOS when using either the Xcode environment or Sandbox. For testing with Xcode, you'll need your iPhone or iPad to run iOS or iPadOS 15.2 or later. To test with Xcode on your Mac, you'll need macOS 12.1 or later. Now, let's take a look at testing subscription offer codes. For this, we'll be using our local StoreKit Configuration file. To make a new offer for codes, we select a subscription, and press the "+" under the offer codes table. We then can configure our offer. We'll name this "Free month" and make it a free offer for one month.
Just like in App Store Connect, we select which customers are eligible, and whether the introductory offer can be redeemed with this offer. Let's leave the default settings for now. Now that our code is configured, we'll press "Done". Of course, if you're syncing with App Store Connect your configured offers will show up in this table automatically.
Now that our offer is configured, let's navigate to the store view in the app. I've added this button near the bottom of the view for redeeming a subscription offer. If we open the store view's implementation in Xcode, implementing offer codes is as effortless as adding an offerCodeRedemption modifier to our view, and flipping the isPresented Binding to true when someone taps the button. Let's see how this works.
When we press the button, the redeem sheet appears above our app. In the App Store, people can type in offer codes that you generate in App Store Connect, but in Xcode the testing experience is much more streamlined. We have a list of all the offers for codes in our configuration file, grouped by the subscription they unlock. To redeem, let's tap the offer we just created, and press the redeem button. The payment sheet appears, and we can see the offer for codes will start right after the pay as you go introductory offer.
After subscribing, we'll get a confirmation screen, and we can now close the sheet and verify our app unlocks access to Social Feed+.
If we look at the inspector for this new transaction, we can see the introductory offer is currently applied. Since the offer is pay as you go, the renewals section shows we'll get two more renewals of the introductory offer. After that, the free month code we just redeemed. Then, the standard subscription will renew indefinitely. The inspector makes it very clear what's happening to the state of our subscription, even with complicated scenarios like multiple offers. We just looked at how to configure offer codes in our local StoreKit configuration, and how to test redeeming them on iPhone. Offer codes are a great way to offer flexible promotions to our future and existing subscribers, and now it's easier than ever to get started using offer codes in Food Truck. Now, let's take a look at how to handle these offers using StoreKit. After redeeming the code, both the Transaction.updates and Status.updates sequences will emit new values. We can check the offerType property on the transaction value to see if there is an offer applied to the current transaction. In the case we just looked at the value of offerType will be introductory, because we allowed the subscriber to redeem the introductory offer with the offer for codes. On the renewalInfo value, we can check the offerType property to see what kind of offer will be present in the next renewal. In the case we just looked at, we can expect the initial value to be introductory since we used a pay as you go offer. After two subscription periods we'll see the value switch to code, as we have a code offer stacked. When offerType is code, we can use the offerID property to get the reference name of the applied offer for codes. That's how you test offers for codes in Xcode. You can configure offers for codes starting in Xcode 13.3, and test them on iPhones and iPads running iOS 15.4 or later. Now that we've verified offers for codes work in Food Truck, let's test how our app handles a price increase for Social Feed+. Testing a price increase is really simple in Xcode. To get started, we'll increase the price for the monthly social feed subscription.
This step is optional. You can leave the price the same and still simulate a price increase. Back to the transaction manager, all we need to do is select the latest transaction for a subscription and press "Request Price Increase Consent" in the toolbar.
We can see in the transaction manager our transaction is now in a "Price Increase Pending" state, and if we look at the device we'll notice a sheet appeared above our app, asking to consent to the price increase. This sheet will appear on its own without adding any code, but we took advantage of the new Messages API to customize its behavior.
Let's take a look at how we integrated with the Messages API in the code.
We have a for loop iterating the messages sequence here, and if we get a message like price increase, make sure we don't have a sensitive view presented, like the donut editor. Otherwise, we'll use the DisplayMessageAction to display the message. If the donut editor is presented, we'll hold on to the Message value and display it after the donut editing is finished.
Let's get back to testing.
In the App Store, existing subscribers may get multiple price increase messages at different times until they make a decision to cancel or consent to the price increase. In Xcode, we have full control of when these messages come. Each time we press the button in the transaction manager, we'll get a message again, even if the transaction is already in a price increase state. Now we can test if our deferral logic actually works. So I'll open the donut editor...
And send a message to open the sheet again.
The sheet doesn't appear yet, but if we leave the donut editor, the sheet appears as expected. While we could accept the price increase, or cancel the subscription in the sheet, in reality, users might respond to the price increase via an external source, like an email. To simulate this, we can use the approve and decline buttons in the transaction manager. Since the donut editing experience was so great, I'll consent to the new price by pressing Approve in the transaction manager. Using StoreKit in Xcode makes testing a complicated corner case like a price increase very smooth. Now that we looked at how to simulate a price increase, let's look at how we can use StoreKit to handle price increases in our app.
When testing the price increase status, the status updates sequence will emit new values with every state change. We can detect these updates in our app by checking the priceIncreaseStatus property on the RenewalInfo value. If a customer cancels their subscription due to a price increase, we'll be able to detect this by checking for didNotConsentToPriceIncrease in the expirationReason property. We can also write unit tests around testing price increase. To start, disabling dialogs will allow us to test without actually showing the price increase UI above our app. After purchasing the subscription, we can use the requestPriceIncreaseConsentForTransaction API to start the process, passing in the ID of the latest transaction for the subscription. To verify a test transaction is pending a price increase, we'll check the isPendingPriceIncreaseConsent property. Finally, depending on what we're testing, we can call consentToPriceIncreaseForTransaction or declinePriceIncreaseForTransaction to see how our app responds to finished price increase cases. That's all for testing price increase. Price increase is testable with Xcode 13.3 on all platforms. Note that the price increase message is only testable on iOS 15.4 or later. Finally, let's take a look at subscription billing retry and grace period. Billing retry is a state where an error occurred when trying to renew a subscription, like an expired credit card. In the App Store, during billing retry the App Store will attempt to fix the issue and recover the subscription. You can optionally enable a grace period, which allows people to keep using their subscription for a limited time, at the beginning of the billing retry state. Let me demonstrate how to simulate this when testing in Xcode. To simulate billing issues on a subscription renewal, we open the "Editor" menu on the StoreKit Configuration we're testing with and enable "Billing Retry on Renewal".
I want Food Truck to support a billing grace period, so let's enable "Billing Grace Period" in the menu as well.
We'll speed up the subscription rate too, so we can watch how the state changes.
Let's first subscribe to Social Feed+.
Now, let's wait for it to be time for a renewal.
When the transaction expires, notice we first enter the billing grace period state. We can look at the transaction inspector and see the time each state will end.
The billing grace period just expired, and now we're in the standard billing retry state. At any time we can use the "Resolve Issue For Transaction" button to simulate fixing the billing error. Let's test resolving the issue.
Now that the issue is resolved, we get a new transaction.
So long as we have "Billing retry on renewal" enabled, each new transaction will continue to enter billing retry, so we can repeat this test as many times as we'd like. Handling Billing Retry and Grace Period properly is key to retaining subscribers by reducing involuntary churn. We just looked at how straightforward it is to simulate these states with Xcode, so now let's go over how to handle them using StoreKit.
As the billing retry and grace period states change, the status updates sequence will emit a new value. Since we offer a billing grace period in Food Truck, we need to make sure to give subscribers access to Social Feed+ while they're in the grace period. We can see how long the subscriber's grace period should be using the gracePeriodExpirationDate property on the renewal info. To check for billing retry, we just have to check isInBillingRetry.
We can also detect either of these states easily with the state property of Status. If we see a customer is in either of these states, we can direct them to a deep link to the App Store to fix the billing issue. If you're using any current entitlement API, you'll receive transactions for expired subscriptions while they're in the grace period. We can also control billing retry and grace period in our unit tests by setting billingGracePeriodIsEnabled and shouldEnterBillingRetryOnRenewal on our StoreKit test session. After our app notices a subscription enter billing retry, the test transaction's hasPurchaseIssue property will be true. After waiting for various status updates and asserting our app updates as expected, we can use the resolve issue for transaction method to simulate the App Store recovering the subscription. Billing retry and grace period are testable in Xcode 13.3 or later on all platforms. Later in the session, Peter will go more into detail on how to test these states in Sandbox on iOS and iPadOS 16.
We covered advanced test cases from requesting refunds to handling billing retry and grace period. For more details on how to use the new StoreKit APIs to support some of these cases, check out "What's new with in-app purchase." This was just a quick overview of what's new for StoreKit Testing in Xcode this year, but we didn't cover everything. There are new subscription renewal rates, you can test the StoreKit 2 in-app manage subscriptions sheet in Xcode, and you can write unit tests for your SKAdNetwork implementations using StoreKitTest. Check out "What's new in SKAdNetwork" to learn more. Now Peter will walk you through what's new in the Sandbox testing environment this year. Peter: Thanks, Greg. Hi, I'm Peter, an App Store server engineer. We saw how new features with StoreKit Testing in Xcode can help you test more complex in-app purchase implementations.
We're constantly listening to your feedback, and we know many of you rely on the App Store Sandbox environment to test your in-app purchases and server implementations. I'm excited to share some new enhancements we're making in Sandbox so you can more easily test your app and server in an online test environment. We'll be introducing enhancements to Sandbox Apple ID creation, the App Store Connect API, and billing failure simulation. To use the sandbox environment, we first need to set up a Sandbox Apple ID in App Store Connect.
You'll notice we moved the Sandbox tester list to the navigation bar on the Users and Access page. Here, we can create a new tester with the Plus button. We streamlined the creation process by removing several fields from the new tester window. We're now only asking for the minimum amount of info, so you can move forward with creating your account without unnecessary information. You can also use a "plus symbol" in your email address, so you don't need to create a brand-new email address for each tester. We know creating strong passwords can be tedious, and we made this easier too. We've also added in-line suggestions for helping make your password more secure.
We hope streamlined Apple ID creation form, and better password complexity hints, will help you spend less time setting up accounts and more time developing your app. App Store Connect is the central location where you create and manage Sandbox Apple IDs, as well as manage your app content and organization. Over the last few years, we've been adding features to Sandbox that you've been asking for, like changing Sandbox account region and clearing purchase history. Many of these features are accessible in App Store Connect or on-device in the Sandbox Manage Subscriptions page. Later this year, we will be bringing several of these Sandbox features to the App Store Connect API, including querying for a list of Sandbox Apple IDs, clearing purchase history, and setting interrupted purchase state. This will enable faster testing with your sandbox accounts and help you setup automation clients for commonly-used testing tools. Finally, I'm happy to announce support for billing failure simulation in Sandbox. In 2018, we announced Billing Retry and Grace Period for auto-renewing subscriptions, to help you reduce involuntary churn. Since launching in 2019, Billing Grace Period has allowed you to recover 300 million days of paid service to your customers. This results in incremental revenue for your business while your customers experience no interruption in service. While many of you are already handling billing failure cases in production, we want to provide more testing scenarios in Sandbox, so you can test and handle billing failures before your app is published on the App Store. You'll be able to use a new Sandbox Account Settings page to enable billing failure simulation for your account, test foreground and background subscription failures in the context of your app, and verify subscription status with verifyReceipt, App Store Server API, and App Store Server Notifications V2 in Sandbox. For more info on Billing Retry and reducing involuntary churn, I recommend the 2018 WWDC session, "Engineering Subscriptions". This year, we're introducing a switch in the new Sandbox Account Settings to simulate a failed in-app purchase attempt. This is also the new home for the Sandbox subscriptions page. With billing failure simulation enabled, foreground in-app purchases will fail. This behavior matches the behavior when your customer's payment method is declined. Billing failure simulation also ensures that auto-renewing subscription states match those for billing failures in production. This means you can test in-app messaging for your customers who are having billing problems. These subscription states will be reflected in your in-app purchase receipts, verified with V2 Notifications. Let's review the subscription lifecycle. When you purchase an auto-renewing subscription in Sandbox, you already receive V2 Notifications, like SUBSCRIBED, and DID_RENEW. When you test failed in-app purchase attempts for an account with an active subscription, the next renewal will fall into billing retry state. You'll now receive billing retry notifications in Sandbox, like DID_FAIL_TO_RENEW. If you disable billing failure simulation before we stop trying to recover the renewal of your subscription, the next renewal attempt will be successful, and you will receive a DID_RENEW notification, with subtype BILLING_RECOVERY. If we reach the limit for retry attempts, and billing failure simulation is enabled, the subscription will expire and you will receive EXPIRED, with subtype BILLING_RETRY. If you're already using Grace Period in production, and V2 Notifications in Sandbox, you can expect to receive the DID_FAIL_TO_RENEW notification with subtype GRACE_PERIOD. Here's an example subscription in billing retry state, with Grace Period. You will receive DID_FAIL_TO_RENEW notifications with subtype GRACE_PERIOD, as well as GRACE_PERIOD_EXPIRED, if billing failure simulation is still enabled at the end of the grace period. When verifying subscription information with App Store Server API, you can verify subscription state by decoding the payload of signedRenewalInfo. Here, we see the expirationIntent and billing retry fields are populated. When calling /verifyReceipt with a receipt for subscriptions in billing retry state, you will see that the is_in_billing_retry_period flag is set to 1. Also, when using grace period, you can now expect grace period expiration date fields to be populated.
Once you've completed testing a billing failure in Sandbox, you can disable the switch in Sandbox Account Settings. We hope this new testability helps you build the best possible experience for your customers. Today we discussed several new testing capabilities you can use to streamline testing your app's in-app purchase functionality. By syncing your configuration in App Store Connect with Xcode, you can use the same in-app purchase configuration when testing locally or with the Sandbox environment. New capabilities like offer code and refund testing in Xcode will help you verify complex StoreKit implementations. And, subscription management testability will enable you to evolve your app to ensure a great customer experience, even if their service is interrupted. For details on how a billing failure impacts subscription receipts, plus, App Store Server Notifications V2 in Sandbox, I recommend the WWDC 21 session, "Manage in-app purchases on your server." Also, to hear about what's new in App Store Server API and V2 Notifications, check out "What's new with in-app purchase." We look forward to hearing your feedback about these new features. Thank you for joining.
-
-
6:58 - Subscription option view
VStack(alignment: .leading) { Text(subscription.displayName) .font(.headline.weight(.semibold)) Text(subscription.description) }
-
11:18 - Refund view
struct RefundView: View { @State private var selectedTransactionID: UInt64? @State private var refundSheetIsPresented = false @Environment(\.dismiss) private var dismiss var body: some View { Button { refundSheetIsPresented = true } label: { Text("Request a refund") .bold() .padding(.vertical, 5) .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .padding([.horizontal, .bottom]) .disabled(selectedTransactionID == nil) .refundRequestSheet( for: selectedTransactionID ?? 0, isPresented: $refundSheetIsPresented ) { result in if case .success(.success) = result { dismiss() } } } }
-
12:33 - Refunds emit an updated value from the transaction updates sequence
for await update in Transaction.updates { let transaction = try update.payloadValue if let revocationDate = transaction.revocationDate, let revocationReason = transaction.revocationReason { print("\(transaction.productID) revoked on \(revocationDate)") switch revocationReason { case .developerIssue: <#Handle developer issue#> case .other: <#Handle other issue#> default: <#Handle unknown reason#> } <#Revoke access to the product#> } <#...#> }
-
14:21 - Offer code view
struct SubscriptionPurchaseView: View { @State private var redeemSheetIsPresented = false var body: some View { Button("Redeem an offer") { redeemSheetIsPresented = true } .buttonStyle(.borderless) .frame(maxWidth: .infinity) .padding(.vertical) .offerCodeRedeemSheet(isPresented: $redeemSheetIsPresented) } }
-
for await verificationResult in Transaction.updates { guard case .verified(let transaction) = verificationResult else { <#Handle failed verification#> } <#Handle updated transaction#> } for await updatedStatus in Product.SubscriptionInfo.Status.updates { guard case .verified(let renewalInfo) = updatedStatus.renewalInfo else { <#Handle failed verification#> } <#Handle updated status#> }
-
16:31 - Check the active offer on the transaction value
for await status in Product.SubscriptionInfo.Status.updates { let transaction = try status.transaction.payloadValue let renewalInfo = try status.renewalInfo.payloadValue if let currentOfferType = transaction.offerType { switch currentType { case .introductory: <#Handle introductory offer#> case .promotional: <#Handle promotional offer#> case .code: <#Handle offer for codes#> default: <#Handle unknown offer type#> } self.hasCurrentOffer = true } <#...#> }
-
16:49 - Check the next pending offer on the renewal info value
for await status in Product.SubscriptionInfo.Status.updates { let transaction = try status.transaction.payloadValue let renewalInfo = try status.renewalInfo.payloadValue <#Check active current offer#> if let nextOfferType = renewalInfo.offerType { switch currentType { case .introductory: <#Handle introductory offer#> case .promotional: <#Handle promotional offer#> case .code: print("Customer has \(renewalInfo.offerID) queued") <#Handle offer for codes#> default: <#Handle unknown offer type#> } self.hasQueuedOffer = true } <#...#> }
-
18:45 - Messages updates loop
private var pendingMessages: [Message] = [] private func updatesLoop() { for await message in Message.messages { if <#Check if sensitive view is presented#>, let display: DisplayMessageAction = <#Get display message action#> { try? display(message) } else { pendingMessages.append(message) } } }
-
20:53 - Price increase changes emit an updated value from the status updates sequence
for await status in Product.SubscriptionInfo.Status.updates { let renewalInfo = try status.renewalInfo.payloadValue if renewalInfo.priceIncreaseStatus == .agreed { print("Customer consented to price increase") <#Handle consented to price increase#> } if renewalInfo.expirationReason == .didNotConsentToPriceIncrease { print("Customer did not consent to price increase") <#Handle expired due to not consenting to price increase#> } <#...#> }
-
21:19 - Unit testing price increases
let session: SKTestSession = try SKTestSession(configurationFileNamed: "<#Configuration name#>") session.disableDialogs = true <#Purchase a subscription#> var transaction: SKTestTransaction! = session.allTransactions().first session.requestPriceIncreaseConsentForTransaction(identifier: transaction.identifier) transaction = session.allTransactions().first XCTAssertTrue(transaction.isPendingPriceIncreaseConsent) <#Assert app updates for pending price increase#> // Write a test case for consenting and cancelling due to price increase: session.consentToPriceIncreaseForTransaction(identifier: transaction.identifier) // OR session.declinePriceIncreaseForTransaction(identifier: transaction.identifier) session.expireSubscription(productIdentifier: "<#Product ID#>") <#Assert app updates for finished price increase#>
-
24:57 - Billing retry and grace period status changes emit an updated value from the status updates sequence
for await status in Product.SubscriptionInfo.Status.updates { let renewalInfo = try status.renewalInfo.payloadValue if let gracePeriodExpirationDate = renewalInfo.gracePeriodExpirationDate, gracePeriodExpirationDate < .now { print("In grace period until \(gracePeriodExpirationDate)”) <#Allow access to subscription#> } else if renewalInfo.isInBillingRetry { <#Handle billing retry#> } <#...#> }
-
25:27 - Using the state property of a status value to check for billing retry states
struct SubscriptionStatusView: View { let currentSubscription: Product let status: Product.SubscriptionInfo.Status @Environment(\.openURL) var openURL var body: some View { Section("Your Subscription") { <#...#> if status.state == .inBillingRetryPeriod || status.state == .inGracePeriod { VStack { Text(""" There was a problem renewing your subscription. Open the App Store to update your payment information. """) Button("Open the App Store") { openURL(URL(string: "https://apps.apple.com/account/billing")!) } } } } } }
-
25:41 - Current entitlement APIs will account for grace period
for await entitlement in Transaction.currentEntitlements { <#Grant access to product#> }
-
25:50 - Unit testing billing retry and grace period
let session: SKTestSession = try SKTestSession(configurationFileNamed: "<#Configuration name#>") session.billingGracePeriodIsEnabled = true session.shouldEnterBillingRetryOnRenewal = true <#Purchase a subscription#> wait(for: [<#XCTExpectation#>], timeout: 60) let transaction: SKTestTransaction! = session.allTransactions().first XCTAssertTrue(transaction.hasPurchaseIssue) <#Assert app still allows access to subscription due to grace period#> wait(for: [<#XCTExpectation#>], timeout: 60) <#Assert app detects billing retry and no longer allows access to subscription#> session.resolveIssueForTransaction(identifier: transaction.identifier) <#Assert app allows access to subscription#>
-
-
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.