Great customer support is critical to running a successful business on the App Store. Discover how you can provide a frictionless support experience to customers who make in-app purchases, including APIs that make it easy for customers to manage or cancel their auto-renewable subscription, or request refunds from directly within your app. We'll cover best practices for handling refunds, as well as additional APIs that can help you better support your customers.
Hello, and welcome to WWDC. My name is Manjeet Chawla, a technical program manager at the App Store, and I'm really excited to talk about some new features that will help you support customers and handle refunds. This is the third session of a three-part series focusing on in-app purchases. And if you haven't already watched "Meet StoreKit 2" or "Manage in-app purchases on your server," I recommend you take a look after this session so you get the full story. In this session, I'll first talk about customer support and how you can provide contextual support to your customers. And because refunds are a critical part of support, my colleague, Joe, will talk about handling refunds and a new server API to inform and improve the refund process. Let's start with supporting customers, the benefits and the challenges of providing scalable support as your business grows on the App Store. Whether your app offers auto-renewing subscriptions or one-time in-app purchases like consumables or non-consumables, we are introducing new StoreKit and App Store server APIs for you to help resolve customer support issues in a timely and efficient manner. And in addition to providing support, these APIs will help you manage your relationships with existing customers after you initially acquire them, increase your overall retention, improve customer satisfaction, which can lead to higher engagement, and reduce churn that grows your long-term revenue. Today, if customers need help for their in-app purchase, they may either contact Apple or you, the developer. And based on the scenario, customers can either use Apple's self-service website, "Report-A-Problem," or contact Apple Support via phone, email, or chat to address the issue. Alternatively, they may also contact you via social media, forums, or live-chat inside the app. And when they contact you about an in-app purchase, the issue may fall into one of these scenarios. From identifying customer's in-app purchases or refunds, to providing compensation for a service issue or an outage, or helping them manage their subscription or request a refund, these questions cover most of the support scenarios, and I'll walk through each scenario in more detail. Let's start with the first scenario, how do you identify the purchases made by a customer when they first contact you for support? Now, if you've purchased any content on the App Store, you may already have seen this email. Today, when customers make an in-app purchase, they receive an invoice for that purchase via email. This invoice contains an order ID, which is unique for each invoice. And customers have access to this via email or by looking at the purchase history under account settings. And now, when a customer contacts you for support, you can ask the customer for the order ID on their invoice and use a new server-to-server API to lookup in-app purchases for the invoice presented by the customer. In addition to validating the invoice, this API also helps you identify any issues with the in-app purchase. For example, if the invoice contains any purchases that have already been refunded by the App Store. Now, let's take a look at how this API works. So now, when a customer contacts your support team, you can ask the customer for the invoice order ID, and your server can call the invoice lookup API, and, in response, the App Store returns a status and the transactions for that invoice signed in a JWS format. And finally, using this information, you can provide support for the correct in-app purchases. To implement this API on your server, you can call the lookup endpoint with the invoice order ID in the URL and the Apple ID for the app in the request. The response includes a signedTransactions object, which contains the transactions for that invoice signed in a JWS format. You can decode the payload for each transaction to get the details of the purchase. Now, let's take a closer look at the new status field. This field identifies the overall status of the invoice. The possible values are 0, meaning the invoice is valid and contains transactions for this order ID, 1, meaning the order ID is invalid, and 2, meaning the invoice is valid, but no matching transactions were found for this order ID. Now, let's review an example of how you can use the response from this API. Here's a sample customer account database where you might be storing the originalTransactionId for each customer's in-app purchase, along with the productID and the purchase date. Using this API, you can link the invoice order ID with the customer's in-app purchases when they contact you about an issue. For example, if this customer purchased coins in your app, and if they contact you for support, you can store the invoice order ID for the purchased coins. Now, let's take a scenario where you have the originalTransactionId for a customer, and you want to lookup their past refunds. Today, you might be relying on verifyReceipt API or App Store server notifications to get notified about refunds. However, if there was an outage, and your server did not receive any notifications from the App Store, how do you lookup this customer's past refunds? We are introducing a new server-to-server API to lookup refunded transactions for a customer using the original transaction ID for any of their in-app purchases within your app. This API enables you to handle an outage or scheduled maintenance by looking up refunds at any time in a quick and easy manner. Additionally, this API can also help you identify the customer's entire refund history for your app. For example, if your app offers both subscriptions and consumables, this API returns all the refunded transactions across all your content types. To implement this API on your server, you create the request with original transaction ID in the URL and the Apple ID for the app in the request parameters. The response contains a list of refunded transactions signed in a JWS format. You can get the all the information needed about the purchases by decoding the payload for each transaction. So, going back to the sample customer account database, you can now use the information returned by this API to update the refunded transactions for this customer using the original transaction ID.
Now, after you've identified there was a service issue, how do you compensate customers? Today, there a few different options to consider. For games, you might be providing some form of in-app compensation in the form of virtual currency or content. Or for subscriptions, you may want to offer a discount on their next renewal. So, how do you compensate subscribers for a service issue? In iOS 14, we introduced a new feature called Subscription Offer Codes to help you acquire, retain, and win back subscribers by providing a subscription at a discount or free for a limited time. You can distribute these unique, one-time codes either using online or offline channels. And for customer service issues, you can provide offer codes as a compensation for the issue, which helps improve retention. You may also use this as an opportunity to suggest an alternative subscription. For example, a longer duration plan that provides more value at a lower price. And customers on iOS 14 and iPadOS 14 and later can redeem offer codes on the App Store through a one-time code redemption URL or within your app, if you've implemented the presentCodeRedemptionSheet API in StoreKit. Now, let's take a look at a sample code-redemption flow within your app. The only custom UI you need to create is the one that initiates the redemption flow. There are several natural places to provide this UI. For example, in your app's settings screen or inside a live-chat functionality when the customer is chatting with your support agent. After the customer taps the redeem button, the system automatically provides a series of code-redemption screens, like the ones shown here, for the customer to enter the code and redeem the offer.
Now, let's take a look at a scenario where there was an outage or an event was canceled, which might be more common for streaming-based apps, such as sports, live TV, or video. For these outages or canceled events, how can you appease customers? We are introducing a new server-to-server API for auto-renewable subscriptions, to extend the renewal date for a paid active subscription. With this API, you can offer free service to your customers for additional time, which can be used as an appeasement for temporary outages or service issues. You can move the renewal date for a customer's subscription twice per calendar year, each up to 90 days in the future, giving you flexibility to resolve service issues or outages. Note that the extension period does not count towards the one year of paid service needed to receive an 85% proceeds rate. Now, let's see how you can implement this API on your server. The request for this API requires an original transaction ID for the customer's subscription, the extension duration, in days, and a reason code for the extension. The response contains the transaction ID that was passed in the request, the web order line item ID for the extended renewal, a success flag to indicate whether the request succeeded and an effective date for the extension if the request succeeded. Now, let's take a look at two different scenarios where you can use this API. In the first scenario, when a customer contacts your support team for a service issue or an outage, you can appease the customer by calling this API, and, in response, the App Store extends the subscription and notifies the customer via email. Or a second scenario, if there is a cancellation to a sports match due to unforeseen situations, or an interruption to a live-streamed event, your support team can proactively use this API, and in response, the App Store extends the subscription and notifies the customers via email.
Now, for a scenario where a customer wants to manage their subscription, how can you enable customers to manage their subscription right inside the app? We are introducing a new StoreKit 2 API to display the manage subscriptions page, which enables you to provide subscription management functionality right within your app, without redirecting customers to the App Store. Optionally, you can also present a save offer before they see the manage subscriptions page, or an exit survey after they cancel to get the cancellation reason. And with this API, you can also test managing subscription purchases in the sandbox environment.
This API is really simple to implement, and it takes one line of code. You simply call the showManageSubscriptions() method in StoreKit 2 to display the manage subscriptions page. Now, let's take a look at a sample manage subscription UI in your app. Under account settings, we can add an option for the user to manage their subscription. Once the customer taps on this button, the App Store will display the existing manage subscriptions page, with the currently-active subscription and the renewal options. This is the same view customers are familiar with with when they visit manage subscriptions under account settings in the App Store, where they can view, upgrade, downgrade, or cancel their subscription. Now, if the customer selects to cancel their subscription, they will see a confirmation screen with the cancellation details and the service expiration date. And for any action the user may take on this page, your server will receive an App Store server notification, and your app will be notified if you've implemented the new StoreKit 2 API. Finally, if a customer is unhappy with their purchase and wants to request a refund, they shouldn't have to leave your app to get help. So, how can you enable customers to request a refund inside your app? We are now introducing a new StoreKit 2 API called beginRefundRequest that enables customers to request a refund for an in-app purchase directly from within your app. And if the refund is approved, your app will be notified and your server receives a REFUND notification from the App Store. Or if the refund is denied, your server receives a new REFUND-DECLINED notification. And for the first time, using this API, you can now initiate and test refunds in sandbox, right within the app. To implement this API, you simply call the beginRefundRequest method with the transaction ID for that purchase. And after the request is submitted, you can handle errors using the do-catch statement. For example, if this was a duplicate request for a transaction that was already refunded, or if the request failed for some other reason, the error code reflects the status of the refund request. Here's a sample refund request UI in your app. In the Help page, now there's a new option to "Request a Refund." Upon selection, the app displays purchases for that customer to request a refund. And if the power surge purchase did not work as expected, the customer can tap on that purchase to invoke a refund request sheet, which contains the purchase details and a list of reason codes for the customer to select. Once the request is submitted, in addition to the in-app confirmation screen, the App Store also sends the customer an email with a link to Apple's "Report a Problem," where they can check the status of their refund. So, with the new APIs, you can now provide contextual and seamless support for in-app purchases right within your app and across other support channels. Providing good support increases overall retention, improves customer satisfaction, which leads to higher engagement, and ultimately, more positive ratings and reviews. In other words, it's a better experience for everyone. Now, we talked about how you can offer customers a way to request refunds using the new refund request API, but there is a lot more to refunds that happens after initiating a request. So, I'm going to invite my colleague, Joe, to talk more about handling refunds and a new opportunity with regards to refund decisioning. Thanks, Manjeet. Hello, my name is Joe Mani, and I'm a program manager at the App Store. Refunds are a sensitive topic, and here at the App Store, we take it seriously. It affects a small percentage of transactions, but we understand the impact it has on your apps. I want to start with a quick recap on refund notifications, which was launched at WWDC20. Then, I will give some insight into how we handle refunds. Finally, we will talk about a new feature, which will help you inform and improve the refund process. In WWDC20, we announced a new notification type called REFUND. After a customer is issued a refund, the App Store sends the REFUND notification to your server. If you have configured a server URL in App Store Connect, you may already be receiving REFUND notifications. When your server receives this notification, respond with a successful HTTP status code 200. Then, you can take appropriate action for the refund in response. Since the launch of REFUND notifications, we've had a chance to hear your feedback, and I'd like to share some best practices with you. Find the best response strategy that works for your business model. For example, if a user purchases in-game currency, and then requests a refund, you can deduct the balance from their account after your server receives the REFUND notification. While, for a subscription, you can revoke access to the service once the subscription has been refunded and canceled. Consider the impact on game design when identifying your response strategy. Use marketing and promotional tools to re-engage customers, and always provide clear messaging to your customer across your communication channels on any actions you have taken.
Let's look at a sample refund timeline for an app that offers coins as in-game currency. After a customer purchases 100 coins, they may immediately spend those coins within your game. If the customer then requests a refund using the new request refund API, or by contacting Apple Support. And if the refund is approved, the App Store will issue a refund, send your server a refund notification, and notifies the customer as well. And this usually happens within a 48-hour window. Now, let's take a look into what happens after the refund is requested and before the App Store issues a decision. At a high level, each refund request will go through our Refund Decisioning System to render a decision. The Refund Decisioning System includes information about the transaction at issue and other factors, such as the customer's purchase and refund history. Now, we've heard from you that you like to have a more active role in refund decisions, so we're excited to announce a new feature that will you improve and inform the refund process. With the new Consumption API, you can share information about a customer's in-app purchase with the App Store. When a customer requests a refund for a consumable in-app purchase, the App Store will send your server a new notification called CONSUMPTION-REQUEST, for you to respond back with the consumption data. In most cases, customers start consuming content soon after they purchase it, and knowing this information is helpful in the refund decision process. Make sure to send consumption information to the App Store with 12 hours of receiving the CONSUMPTION-REQUEST, so that it can be used to inform the refund decision. Now, let's take a look at what fields are included within the consumption data. The consumption payload contains the following data points, each of which helps to inform the refund decision.
First, you include the original transaction ID for the in-app purchase in the request URL. Set the customerConsented field to "true" if the user has consented to sending the requested consumption API data to Apple, in order for Apple to use that data in decisioning. The consumptionStatus field is important. Use it to indicate if the user has consumed the in-app purchase partially, fully, or not at all. For example, if your app has an exchange platform that has bartering or has an in-app purchase that has been transferred from one account to another, it is considered consumed. The consumption platform field identifies if your app is cross platform and where it was consumed. Use the sampleContent field to indicate if you provided a free sample or trial to the user, or if the user was given a similar in-app purchase within the app. Alternatively, use this field to indicate if the user was provided information about the in-app purchase and the expected game play or mechanics prior to the purchase. Use the deliveryStatus field to indicate the in-app purchase was successfully delivered to the customer and that it functioned properly. The appAccountToken is a new field introduced in StoreKit 2. This will be the UUID associated with the app's user account that you create that is initiating the purchase and consuming the content for the purchase. The remaining fields include information about how long the user has had an account, how much time they've played in your app, their total spend, and the current status of their account. For a refund request, there are three related App Store server notifications: the new CONSUMPTION-REQUEST notification to notify you when a refund request has been initiated for a consumable in-app purchase. For all content types, the REFUND notification notifies you when a refund is issued to a customer. And for all content types, the REFUND-DECLINED notification notifies you when a refund was declined for a request that was initiated using the Store Kit API. Now let's return to the refund timeline. When the customer requests a refund for a consumable in-app purchase, the App Store server will now send your server a consumption request notification. Your server responds back within 12 hours with the consumption data back to the App Store server, which will then be used for decisioning. If it is approved, the App Store will send the REFUND notification, and after your server responds with an HTTP OK response, you can take appropriate action for that refund.
And the consumption API is available both in production and to test in sandbox as of today. Now let's cover some of the benefits of sending information to Apple with the new Consumption API. Obtaining these data points will help increase transparency and improve our overall refund process. This, in turn, provides a better overall outcome to your customers. Also, with the new REFUND notification, you have more opportunities to reach your customer, thus increasing the overall communication. Now, I'd like to pass it back to my colleague, Manjeet, to share some key take-aways from everything we've covered. So, we covered a lot of topics today. Let me go through the key takeaways for this session. With the new StoreKit APIs, you can now implement a custom help UI in your app for customers to request a refund and for subscriptions, a way to manage their subscription within the app. Review and optimize your customer support journeys by implementing the new server-to-server APIs. For example, use the invoice lookup API to identify and validate a customer's in-app purchase. And if you haven't already, setup your server to receive the refund, consumption request, and other status update notifications from the App Store. Identify the response strategy that works best for your app's business model to take action upon refunds. And finally, you can now inform Apple's refund decisioning system by responding to consumption request notifications from the App Store by sending latest consumption data. So, this was "Support customers and handle refunds." For more information on the new StoreKit 2 APIs, watch "Meet StoreKit 2," and for more information on building server-side logic for in-app purchases, watch "Manage in-app purchases on your server." Thanks for listening today, and enjoy the rest of WWDC. [percussive music]
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.