<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Surik Sarkisyan</title>
    <description>The latest articles on DEV Community by Surik Sarkisyan (@ssarkisyan).</description>
    <link>https://dev.to/ssarkisyan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F874775%2F8af83904-8d3a-4b79-96a5-f4af196fe31c.png</url>
      <title>DEV Community: Surik Sarkisyan</title>
      <link>https://dev.to/ssarkisyan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ssarkisyan"/>
    <language>en</language>
    <item>
      <title>What does restore purchase mean?</title>
      <dc:creator>Surik Sarkisyan</dc:creator>
      <pubDate>Sun, 23 Oct 2022 20:20:15 +0000</pubDate>
      <link>https://dev.to/qonversion/what-does-restore-purchase-mean-5afg</link>
      <guid>https://dev.to/qonversion/what-does-restore-purchase-mean-5afg</guid>
      <description>&lt;p&gt;When users purchase non-consumables, auto-renewable subscriptions, or non-renewing subscriptions, they expect them to be available on all their devices and indefinitely. &lt;/p&gt;

&lt;p&gt;For the cases when a user upgrades to a new phone or reinstalls the app, you should provide them with UI functionality for restoring purchases. This allows them to regain access to any previously purchased content without paying again. But how can you create the capability and ensure it works across both iOS and Android? &lt;/p&gt;

&lt;p&gt;In this article, we will explore what does restore purchase mean, how to configure it on iOS and Android, and the primary differences between them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does restore purchase mean?
&lt;/h2&gt;

&lt;p&gt;The restore purchase capability is a functionality (most commonly seen as a “Restore Purchase” button) that enables app users to maintain access to subscriptions and other in-app purchases without going through the purchase process again. This mechanism allows your users to restore purchases manually. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eb_QCZOZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z6vx3oga2tb9x2rwg9sp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eb_QCZOZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z6vx3oga2tb9x2rwg9sp.png" alt="Image description" width="880" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This method is &lt;a href="https://developer.apple.com/documentation/storekit/in-app_purchase/original_api_for_in-app_purchase/restoring_purchased_products"&gt;Apple’s recommended approach&lt;/a&gt; to restoring purchases. However, if you enable this automatically, your users will be shown a login screen that interrupts user flow – which is anything but user-friendly. &lt;/p&gt;

&lt;p&gt;In case you have an internal authorization logic that allows you to manage access without prompt screens, you can avoid placing this “Restore Purchase” button. However, the restore button is still the most common practice. This button should trigger the specific Restore method, which will unlock the needed permissions for your users. &lt;/p&gt;

&lt;h2&gt;
  
  
  Cases for restoring purchase
&lt;/h2&gt;

&lt;p&gt;Our recommendation is to have this button by default. Why? Having a restore purchase feature set by default could be useful for your user: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the user has multiple devices signed in to the same account&lt;/li&gt;
&lt;li&gt;If the user upgraded their device &lt;/li&gt;
&lt;li&gt;If the user reset their device to factory settings &lt;/li&gt;
&lt;li&gt;If the app was deleted and reinstalled on a device&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What products could be restored?
&lt;/h2&gt;

&lt;p&gt;There are 3 types of In-App Purchases that can be restored: &lt;/p&gt;

&lt;p&gt;Non-consumables: a type that customers purchase once. They don’t expire.&lt;br&gt;
Auto-renewable subscriptions: services or content that customers purchase once and renew automatically on a recurring basis until customers decide to cancel.&lt;br&gt;
Non-renewing subscriptions: services or content provide access over a limited duration and don’t renew automatically. Customers can purchase them again.&lt;br&gt;
Consumables purchases are not applicable for restoring as they are associated with the account. For example, if a user previously purchased “coins,” these purchases are connected to his account, and there is nothing to restore from StoreKit or Google Play Billing Library.  To learn more about this process, check out our guide on &lt;a href="https://qonversion.io/blog/setting-up-consumable-and-non-consumable-in-app-purchases/"&gt;how to set up consumable and non-consumable purchases&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to configure restore purchases on iOS?
&lt;/h2&gt;

&lt;p&gt;In most cases, restoring purchases on iOS only requires you to refresh the app receipt and redeliver the products listed on the receipt. The refreshed receipt contains a record of the user’s purchases in this app from any device the user’s account is logged into. To restore purchases, use native StoreKit methods. In this guide, we’ll explore the StoreKit method as the most commonly used. We are excited by the potential of &lt;a href="https://qonversion.io/blog/what-is-storekit-2-and-what-are-its-new-features/"&gt;StoreKit 2&lt;/a&gt;, so you can learn more details about it in this &lt;a href="https://qonversion.io/blog/what-is-storekit-2-and-what-are-its-new-features/"&gt;the following article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;SKPaymentQueue restoreCompletedTransactions()&lt;/code&gt; to restore non-consumables, non renewable, and auto-renewable subscriptions. StoreKit notifies the app’s transaction observer by calling &lt;code&gt;paymentQueue(_:updatedTransactions:)&lt;/code&gt; with a transaction state of &lt;code&gt;SKPaymentTransactionState.restored&lt;/code&gt; for each restored transaction. If restoring fails, see &lt;code&gt;restoreCompletedTransactions()&lt;/code&gt; discussion for details on how to resolve it.&lt;/p&gt;

&lt;p&gt;If you use Qonversion to manage your in-app subscription, call this &lt;a href="https://documentation.qonversion.io/docs/making-purchases#5-restore-purchases"&gt;method&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Qonversion.restore { (permissions, error) in
  if let error = error {
    // Handle error
  }
  if let permission: Qonversion.Permission = permissions["plus"], permission.isActive {
    // Restored and permission is active 
  }
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to configure restore purchases on Android?
&lt;/h2&gt;

&lt;p&gt;When compared to Apple, Google doesn’t have a specific process for restoring purchases. As Google mentions, the history of purchases is available in Google’s cache, so users receive all associated permissions automatically. From our experience, it would be better to consider restoring purchase flow, as Google’s cache could consist of unactual information or could be cleaned.&lt;/p&gt;

&lt;p&gt;To restore Google’s purchases, use this method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public abstract void queryPurchaseHistoryAsync (QueryPurchaseHistoryParams queryPurchaseHistoryParams, 
                PurchaseHistoryResponseListener listener).

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There you get the items that are purchased, and that`s it. Take note that Google returns just the last purchase from each Product. &lt;/p&gt;

&lt;p&gt;If you use Qonversion to manage your in-app subscription, call this &lt;a href="https://documentation.qonversion.io/docs/making-purchases#5-restore-purchases"&gt;method&lt;/a&gt;: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;br&gt;
Qonversion.restore { &lt;a href="https://dev.topermissions,%20error"&gt;weak self&lt;/a&gt; in&lt;br&gt;
  if let error = error {&lt;br&gt;
    // Handle error&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;if let permission: Qonversion.Permission = permissions["plus"], permission.isActive {&lt;br&gt;
    // Restored and permission is active &lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we explored what does restore purchase mean and the flow on how to configure restore purchases on iOS and Android. By implementing this feature, you can help customers continue their paid services with ease – and keep positive user reviews coming! &lt;/p&gt;

&lt;p&gt;Want to learn more? We know a thing or two about in-app purchases, as Qonversion provides a complete cross-platform infrastructure that allows you to create and restore purchases, validate receipts, and provide your app with an accurate subscription status without the need to build your server. &lt;/p&gt;

&lt;p&gt;So if you’d like to learn more about it or have any questions, feel free to contact us anytime. We’d love to help you discover how to build better subscription experience with Qonversion! &lt;/p&gt;

</description>
    </item>
    <item>
      <title>How to send in-app subscription events to Firebase, Google Analytics, and Google Ads</title>
      <dc:creator>Surik Sarkisyan</dc:creator>
      <pubDate>Sun, 16 Oct 2022 15:56:24 +0000</pubDate>
      <link>https://dev.to/qonversion/how-to-send-in-app-subscription-events-to-firebase-google-analytics-and-google-ads-3b38</link>
      <guid>https://dev.to/qonversion/how-to-send-in-app-subscription-events-to-firebase-google-analytics-and-google-ads-3b38</guid>
      <description>&lt;p&gt;Google provides massive user acquisition opportunities for subscription apps. Using &lt;a href="https://qonversion.io/blog/how-to-best-promote-your-app-google-app-campaigns/"&gt;Google’s App Campaigns&lt;/a&gt; you can promote your app across Google Search, Google Play Store, YouTube, Discover on Google Search, and the Google Display Network. Besides user acquisition, Google’s Firebase and Google Analytics provide a set of tools to manage and grow your product. This includes advanced A/B testing, push notifications, and analytics. &lt;/p&gt;

&lt;p&gt;But to be able to truly leverage Google’s ecosystem, a subscription app needs to send accurate in-app subscription data to Google, including data on subscription renewals and trial-to-paid conversions. &lt;/p&gt;

&lt;p&gt;For example, though Google’s machine learning does most bidding and performance optimization in Google App Campaigns, it still needs correct revenue data to do so effectively. &lt;/p&gt;

&lt;p&gt;This article covers how to set up conversion tracking for subscription apps with Firebase. The data from Firebase can be used everywhere on Google’s platform, from A/B testing in Firebase to campaign optimization on Google’s App Campaigns. Additionally, we will take a closer look at how conversion tracking affects your Google App Campaigns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Google Analytics for Firebase SDK
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://qonversion.io/blog/how-to-send-in-app-subscription-events-to-firebase-google-analytics-and-google-ads/"&gt;Firebase Google Analytics&lt;/a&gt; is a free app measurement solution that provides insights on app usage, user acquisition, and engagement. Google recommends using this solution as it is most accurate, so if you’re using Google Ads SDK, please consider switching your tool to Firebase. &lt;/p&gt;

&lt;p&gt;When SDK is in place, you’ll automatically receive a number of events that could be used as conversion &lt;a href="https://support.google.com/firebase/answer/9234069?hl=en"&gt;events&lt;/a&gt; (goals) for your Google Ads campaigns.&lt;/p&gt;

&lt;p&gt;However, there are just a few predefined events that make sense for subscription apps: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;app_store_subscription_renew&lt;/code&gt; &lt;br&gt;
&lt;code&gt;app_store_subscription_convert&lt;/code&gt;&lt;br&gt;
&lt;code&gt;app_store_subscription_event_started&lt;/code&gt; (Android only) &lt;br&gt;
&lt;code&gt;app_store_refund&lt;/code&gt; (Android only)&lt;br&gt;
&lt;code&gt;in_app_purchase&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;As you see, the number of subscription events to track is very limited. &lt;/p&gt;

&lt;h2&gt;
  
  
  Firebase Integration: How to send subscription events to Firebase and Google Analytics
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CEgU4-0y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3w3zm66hexk5soq3zar7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CEgU4-0y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3w3zm66hexk5soq3zar7.png" alt="Image description" width="880" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Qonversion enriches Firebase data with subscription revenue events, including purchases, trial conversions, subscription renewals, and even refunds. This allows you to match your users’ behaviour with their payment history in Firebase, empowering your user acquisition and product decisions. Qonversion sends events server-side, which means that renewals and cancellations are sent to Google Analytics in real-time, even if the customer doesn’t open the app. And even more importantly,  implementation takes about 20 minutes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6qMitRJt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u3c4v169xzffvtg9uczo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6qMitRJt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u3c4v169xzffvtg9uczo.png" alt="Image description" width="880" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Firebase track install, In-app purchases and subscriptions events iOS, Android.&lt;/p&gt;

&lt;p&gt;To send subscription events to Firebase, &lt;a href="https://documentation.qonversion.io/docs/firebase"&gt;you need&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up the &lt;a href="https://firebase.google.com/docs/analytics/get-started"&gt;Google Analytics for Firebase SDK &lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Set up &lt;a href="https://documentation.qonversion.io/docs/install-sdk"&gt;Qonversion SDK  &lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Send firebaseAppInstanceId to Qonversion using &lt;a href="https://documentation.qonversion.io/docs/user-properties"&gt;User Properties&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;if let appInstanceID = Analytics.appInstanceID() {&lt;br&gt;
    Qonversion.setProperty(.firebaseAppInstanceId, value: appInstanceID)&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Configure the &lt;a href="https://qonversion.io/blog/how-to-send-in-app-subscription-events-to-firebase-google-analytics-and-google-ads/"&gt;Firebase Integration &lt;/a&gt;
4.1. Copy your Firebase App ID and Measurement Protocol API secret from the &lt;a href="https://analytics.google.com/analytics"&gt;Google Analytics Console&lt;/a&gt;: Admin → Data Streams → Select existing or create a new stream for your App.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--esKqLQw2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kvw8e5nsrvo2ffoa688o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--esKqLQw2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kvw8e5nsrvo2ffoa688o.png" alt="Image description" width="880" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;4.2. Navigate to the Tools → Integrations section in your Qonversion account.&lt;/p&gt;

&lt;p&gt;4.3. Choose your platform (IOS or Android) and click the Add new + button and select Firebase.&lt;/p&gt;

&lt;p&gt;4.4. Provide the API Secret and App ID copied in the first step to the corresponding fields.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FPkeQQTD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lg5debko0ie9j5d94ktq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FPkeQQTD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lg5debko0ie9j5d94ktq.png" alt="Image description" width="880" height="529"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That’s it! From now on, all subscription events will be visible in Firebase. &lt;/p&gt;

&lt;h2&gt;
  
  
  How to link Firebase Google Analytics to Google Ads
&lt;/h2&gt;

&lt;p&gt;This step is necessary if you’d like to optimize your Google Ad campaigns based on Firebase conversion tracking events. &lt;/p&gt;

&lt;p&gt;To successful setup, you need to &lt;a href="https://documentation.qonversion.io/docs/google-ads"&gt;perform four steps&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Firebase =&amp;gt; Project Settings&lt;/li&gt;
&lt;li&gt;Click Integrations tab &lt;/li&gt;
&lt;li&gt;Link Google Ads to Firebase project&lt;/li&gt;
&lt;li&gt;Enable events as conversions: On the Events tab, in the row for Qonversion events, turn on the switch in the Mark as conversion column. You can have a maximum of 15 conversion events per app in Firebase. Once an event has been enabled as a conversion, it is available in Attribution &amp;gt; Conversion Events. Attribution reporting begins for that event at the time you enable it as a conversion.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The data from Qonversion Firebase Integration unlocks new capabilities for Google App Campaign optimization for subscription apps. Let’s take a deeper look.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Google App Campaigns are
&lt;/h2&gt;

&lt;p&gt;Google App campaigns are user acquisition channels that allow you to promote your app within the Google Network – on Youtube, Discover, Search, Google Play, etc. Unlike most Google Ads campaigns, you don’t need to create individual ads for each campaign. You only need to upload the set of assets: text, images, videos, and a starting bid and budget. Google will automatically design different ad variations across different formats and networks. Google Ads will test different combinations and show better-performing ads more often, with no extra work on your part.&lt;/p&gt;

&lt;p&gt;There are three main types of Google App Campaigns: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Google App Campaigns for installs. &lt;/li&gt;
&lt;li&gt;Google App Campaigns for pre-registrations. &lt;/li&gt;
&lt;li&gt;Google App Campaigns for engagement. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The insights of this article could be the most useful for Google App Campaigns for Engagement and Google App Campaigns for Installs optimization. You can check more details on &lt;a href="https://qonversion.io/blog/how-to-best-promote-your-app-google-app-campaigns/"&gt;Google App Campaigns&lt;/a&gt; here.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to optimize Google Ad based on Firebase conversion events data
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Google App Campaigns optimization by conversion event
&lt;/h2&gt;

&lt;p&gt;You can set the primary conversion event for your Google App Campaign. Google Ads will automatically use this data to learn to optimize your ads, using this parameter to get you the best number of conversions and to optimize your set cost-per-action bid.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://support.google.com/google-ads/answer/9234102?hl=en-GB&amp;amp;ref_topic=11069497"&gt;settings&lt;/a&gt; of your Google App Campaign.&lt;/li&gt;
&lt;li&gt;Choose which in-app actions (conversion event) you want to optimize your campaign for, and then enter a target cost per this action (target CPA).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it! Now Google Ads has enough data to learn to optimize your campaign by specific metrics: trial to subscription, or subscription to renew, etc. The fill list of metrics that you could optimize your campaigns for is &lt;a href="https://documentation.qonversion.io/docs/integrations-overview"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Google App Campaigns optimization by the audience
&lt;/h2&gt;

&lt;p&gt;You can &lt;a href="https://support.google.com/google-ads/answer/6398643"&gt;create audience segments in Firebase&lt;/a&gt; using any combination of events and user properties (for instance, people from UK that subscribed to your app for the first time). &lt;/p&gt;

&lt;p&gt;Then, you could apply your segments for a particular App Campaign. Segments based on mobile app data can be targeted by Search, Display, and Video campaigns.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://support.google.com/google-ads/answer/6398643"&gt;Create a segment in Firebase.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Make sure that your Firebase account is linked to Google Ads.&lt;/li&gt;
&lt;li&gt;Open the &lt;a href="https://support.google.com/google-ads/answer/9234102?hl=en-GB&amp;amp;ref_topic=11069497"&gt;settings&lt;/a&gt; of your Google App Campaign.&lt;/li&gt;
&lt;li&gt;Choose the Target Audience Segment. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it! Now Google Ads has enough data to lern to optimize your campaign for the specific audience – whether they are new or returning users, or people from specific territory. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we explored how &lt;a href="https://qonversion.io/blog/how-to-send-in-app-subscription-events-to-firebase-google-analytics-and-google-ads/"&gt;Qonversion Firebase Integration&lt;/a&gt; allows you to enrich Firebase data and improve the strategy of your user acquisition campaigns. In the following article, we’ll dive deeper into how this integration enables you to attribute user acquisition costs within other &lt;a href="https://firebase.google.com/products/analytics/partners"&gt;Ad Networks&lt;/a&gt; outside Google – AdColony, IronSource, Vungle, and others.&lt;/p&gt;

</description>
      <category>firebase</category>
      <category>subscriptions</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SKErrorDomain Code=0. What is it and how to fix it?</title>
      <dc:creator>Surik Sarkisyan</dc:creator>
      <pubDate>Mon, 10 Oct 2022 08:30:06 +0000</pubDate>
      <link>https://dev.to/qonversion/skerrordomain-code0-what-is-it-and-how-to-fix-it-2k8a</link>
      <guid>https://dev.to/qonversion/skerrordomain-code0-what-is-it-and-how-to-fix-it-2k8a</guid>
      <description>&lt;p&gt;Domain=&lt;a href="http://qonversion.io/blog/skerrordomain-code-0/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=skerrordomain0"&gt;SKErrorDomain Code=0&lt;/a&gt; “(null)” is one of the more common &lt;a href="https://qonversion.io/blog/handling-storekit-errors/"&gt;errors&lt;/a&gt; that you may struggle with while processing in-app purchases. This error can occur in both production and sandbox environments and affects the fail to success payment ratio of your app. &lt;/p&gt;

&lt;p&gt;Though the reason for this error is more likely to be on the App Store’s side, there still are a few things to try.  &lt;/p&gt;

&lt;p&gt;In this article, we’ll give you a brief overview of the possible reasons for the error, and what you could test to prevent it from happening.&lt;/p&gt;

&lt;h2&gt;
  
  
  SKErrorDomain Code=0 in the sandbox environment
&lt;/h2&gt;

&lt;p&gt;When SKErrorCode=0 occurs while in the sandbox environment and not in the production environment, it could be related to issues with the sandbox on Apple’s side and could potentially affect the &lt;code&gt;make payment&lt;/code&gt; call and the &lt;code&gt;/verifyReceipt&lt;/code&gt; endpoint, although it won’t do this in production mode.&lt;/p&gt;

&lt;p&gt;Things you can check: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test your in-app purchases with a real device, not in the simulator&lt;/li&gt;
&lt;li&gt;Check your in-app Product ID&lt;/li&gt;
&lt;li&gt;Create a new sandbox test account on the App Store, then logout from all other accounts, and then test it with the new sandbox test account&lt;/li&gt;
&lt;li&gt;Check whether your in-app purchases are configured correctly on App Store Connect and have the following statuses: Ready to submit or Ready for sale.&lt;/li&gt;
&lt;li&gt;Check if your app bundle ID matches the bundle ID from App Store Connect where the purchases were created.&lt;/li&gt;
&lt;li&gt;Add Localisation (Subscription Display Name and Description) to the product. The hypothesis is that there is no obvious way to see this from the error and therefore you may quickly miss it when you are preparing placeholders for future In-App Purchases. However, it seems that Apple should mention this in its documentation and implement an error code that gives a warning about any missing metadata. &lt;/li&gt;
&lt;li&gt;Reboot/Reset the device&lt;/li&gt;
&lt;li&gt;Check that you agreed with the latest versions of Apple’s policies &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have tried everything from the list above, then it means that the issue is on the App Store’s side and you should follow the information on &lt;a href="https://developer.apple.com/system-status/"&gt;Apple’s status page&lt;/a&gt; or submit a bug report. &lt;/p&gt;

&lt;h2&gt;
  
  
  SKErrorDomain Code=0 during the review process
&lt;/h2&gt;

&lt;p&gt;If you have difficulties during the app review process, then you will have to ask the reviewer to retry the purchase. Also, Apple’s engineers highlight that App Review should not see any issues with sandboxes as they use special accounts for the review process. So if your app was rejected then the issue mostly probably lies elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  SKErrorDomain Code=0 in production environment
&lt;/h2&gt;

&lt;p&gt;If you receive Error Domain=SKErrorDomain Code=0 “Operation could not be completed. (SKErrorDomain error 0.)” in the production environment, try the following:&lt;/p&gt;

&lt;p&gt;Make sure you have used the correct &lt;code&gt;Product Identifier&lt;/code&gt;. If you have, then you’ll get error 0 shortly after calling &lt;code&gt;[SKPaymentQueue addPayment:]&lt;/code&gt;, and before you get the popup asking you to confirm payment.&lt;/p&gt;

&lt;p&gt;Try to create a new test user as your current test user might have been invalidated. This can happen if you accidentally log into the App Store with your test user. When this happens, you’ll get error 0 after entering your password to confirm the payment. &lt;/p&gt;

&lt;p&gt;Unfortunately, not much else can be done regarding this error while in the production environment. This error mostly comes directly from Apple, and is an unknown error to do with their API. If you are using Qonversion to manage in-app subscriptions, we would also recommend checking &lt;a href="https://status.qonversion.io/"&gt;our status page&lt;/a&gt; for any reported outages. Unfortunately, these errors are returned randomly and directly from Apple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Submit a bug report about SKErrorDomain Code=0
&lt;/h2&gt;

&lt;p&gt;When the issue is on Apple’s side, it can affect everyone or just some users. It’s always a good idea to submit a bug report with complete information, including a sysdiagnose archive from the device. To submit the bug report, please use &lt;a href="https://bugreport.apple.com/"&gt;Apple Developer Bug Report&lt;/a&gt; and follow the instructions. Before you begin, make sure you are familiar with triggering the sysdiagnose log, then follow and perform each step to replicate the problem. When the failure occurs, first, trigger the sysdiagnose profile, then wait 5 minutes before syncing the device with Apple Store and then look for the sysdiagnose archive file. While reproducing the issue, you should take screenshots showing evidence of the connection failure.&lt;/p&gt;

&lt;p&gt;We will update this article if and when more details regarding this error become available. If you are getting other Storekit errors, then check out this complete guide on &lt;a href="http://qonversion.io/blog/handling-storekit-errors/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=handling-skerrors"&gt;how to handle storekit errors&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>mobile</category>
      <category>ios</category>
      <category>swift</category>
    </item>
    <item>
      <title>What’s new in StoreKit testing (WWDC22 updates)</title>
      <dc:creator>Surik Sarkisyan</dc:creator>
      <pubDate>Mon, 13 Jun 2022 07:12:57 +0000</pubDate>
      <link>https://dev.to/qonversion/whats-new-in-storekit-testing-wwdc22-updates-4cpe</link>
      <guid>https://dev.to/qonversion/whats-new-in-storekit-testing-wwdc22-updates-4cpe</guid>
      <description>&lt;p&gt;New day, new &lt;a href="https://developer.apple.com/videos/play/wwdc2022/10040/"&gt;WWDC22 session&lt;/a&gt;, new overview! Today we’ll walk you through the updates in StoreKit testing. &lt;/p&gt;

&lt;p&gt;Before we move on, I’d like to emphasize that previous significant updates for StoreKit testing were introduced during WWDC20, which we’ve also &lt;a href="https://qonversion.io/blog/storekit-testing-in-xcode-12-and-ios-14/"&gt;covered here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now, it’s time to move on to the brand new updates! &lt;/p&gt;

&lt;p&gt;What we will cover today:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;StoreKit updates in Xcode 14&lt;/li&gt;
&lt;li&gt;Advanced subscription cases&lt;/li&gt;
&lt;li&gt;Sandbox updates&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  StoreKit updates in Xcode 14
&lt;/h2&gt;

&lt;p&gt;During WWDC20, the StoreKit.configuration file was announced. This unlocked the capability to test in-app purchases without needing to set up the Products in App Store Connect. &lt;/p&gt;

&lt;p&gt;This allowed you to set up in-app products directly in StoreKit.configuration file, and this local file would be the only place that contains these products and purchases.&lt;/p&gt;

&lt;p&gt;The latest WWDC22 updates require you to create the app and products in App Store Connect. You still have an option to test your in-app purchases through StoreKit.configuration directly in Xcode without having an app in App Store Connect. The only limitation of this commonly used approach is that you will not have the chance to use a part of a new features. &lt;/p&gt;

&lt;p&gt;You may wonder: what are the features of this and are they worth all this hassle with app creation in App Store? My answer is: absolutely! &lt;/p&gt;

&lt;p&gt;By having an app with the products in App Store Connect, you can create a new configuration file in Xcode that will synchronise these products with those that were created in App Store Connect. Any changes that you make within App Store Connect – whether changing the title of the product or doing something else – can be synced with this Xcode Configuration file.&lt;/p&gt;

&lt;p&gt;Additionally, you can convert this new file into the local version that won’t be synced with App Store Connect, but will give you an option to edit your products directly in Xcode. Be accurate and careful here, because this is a one-way operation. You will not have a chance to convert the local file back to the synced version. So, in order to proceed with further syncs you’ll need to create a new file. A great option is to have several StoreKit.configuration files; for instance, you could have both synchronized and local ones. &lt;/p&gt;

&lt;p&gt;To create the new StoreKit.configuration file you should go to Xcode Menu =&amp;gt; File =&amp;gt; New (or lock Command + N). Then, type storekit in the search, select StoreKit.configuration file, and push the “Next” button.&lt;/p&gt;

&lt;p&gt;Here’s a screenshot to clarify this process:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--A9xex_XB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mbthc2o91lydyoex5h57.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9xex_XB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mbthc2o91lydyoex5h57.png" alt="Storekit.configuration file" width="880" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Xcode 14 while creating this file you’ll see the checkbox that enables the syncing of in-app products with App Store Connect. To turn this option on, you need to choose the Team and App.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C_Ug8_X---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uvqgdid4l7om1b9mclqp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C_Ug8_X---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uvqgdid4l7om1b9mclqp.png" alt="Storekit.configuration file" width="880" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It will be pretty clear to you that to create the local file you should just fill the name of the file and leave the checkbox unchecked. &lt;/p&gt;

&lt;p&gt;Once you create a configuration file, all the information about your Products begins to sync with App Store Connect. In the lower right corner, you’ll notice the indicator showing you that data downloading is in progress. Even though this job is in progress, you can still continue to work in Xcode. &lt;/p&gt;

&lt;p&gt;Those who used to work with the local version of StoreKit.configuration will notice that the newly created file differs from the files that they used to see. The reason for this is that this local version of StoreKit.configuration is read-only. All the changes in products should be made with App Store Connect. &lt;/p&gt;

&lt;p&gt;As we highlighted before, all the changes in products that were made in App Store Connect can be synced in StoreKit.configuration file just with one button below. Once the sync is complete, you can see the changes reflected in Xcode.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6saBoX1_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8hxyj7gwm4puxsld2kk9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6saBoX1_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8hxyj7gwm4puxsld2kk9.png" alt="Sync" width="880" height="482"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As I mentioned, this is a read-only file. &lt;/p&gt;

&lt;p&gt;You still can easily copy the Product from this file and move it to the local StoreKit.configuration file in Xcode. To do so, right click to the Product that you’d like to copy and click “Copy”. Then choose the local file and open Xcode Menu =&amp;gt; File =&amp;gt; Paste (or hold command + V).&lt;/p&gt;

&lt;p&gt;Alternatively, you can convert this synced file to the local one. You can’t undo this operation, but if necessary you can create a new synced StoreKit.configuration file. Xcode Menu =&amp;gt; Editor =&amp;gt; Convert to Local StoreKit Configuration.&lt;/p&gt;

&lt;p&gt;Let’s move on to the testing.&lt;br&gt;
To start this process you should choose the StoreKit.configuration file in Xcode that should be used for building the app. This is a commonly used method.&lt;/p&gt;

&lt;p&gt;Then open Scheme Editor: &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--86D6MsoS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vuqmz4xdjhbnlxm2oa1n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--86D6MsoS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vuqmz4xdjhbnlxm2oa1n.png" alt="Scheme editor" width="880" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Choose the “Run” action, then “Options”, and then choose the right file in the StoreKit Configuration:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vjliBbPy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/thvaqe2qtg8hl1r8lowj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vjliBbPy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/thvaqe2qtg8hl1r8lowj.png" alt="StoreKit Configuration" width="880" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congrats on the successful set up of your testing environment! Now you can configure your app.&lt;/p&gt;

&lt;p&gt;One more great update for those who use SwiftUI: starting with Xcode 14, all the Products from StoreKit.configuration files will be uploaded to SwiftUI previews. &lt;/p&gt;

&lt;p&gt;Let’s move on to the testing. As you might remember, WWDC20 introduced the Transaction Manager. You can open it by clicking on the “Purchases” icon in the debug bar. &lt;/p&gt;

&lt;p&gt;Xcode 14 Transaction manager includes Transaction Inspector (on the right side) that displays under-the-hood details about a transaction. This is a super convenient way to check subscription statuses. For instance, here you could see the information about expired subscriptions or subscription renewals.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hVUVpYLO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gp3h5lh1q21dmmny0eu3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hVUVpYLO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gp3h5lh1q21dmmny0eu3.png" alt="Xcode 14 Transaction manager" width="880" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, by clicking these buttons you can move to the specific Product in StoreKit.configuration file in Xcode.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1GKvzu2L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zrxt1gemksjyiry6f7cz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1GKvzu2L--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zrxt1gemksjyiry6f7cz.png" alt="Xcode 14 Transaction manager" width="880" height="634"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Moreover, at the bottom part of the inspector, there is a field to filter transactions. For instance, you can filter by the day of creation of the transaction or by Product ID. This significantly simplifies the way you navigate all your transactions (it is quite a daunting task, as you have all subscription renewals that may confuse your detection of which transaction entitles the feature).&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced subscription cases
&lt;/h2&gt;

&lt;p&gt;The new version of Xcode allowed the chance to test things in a more convenient way, and to test something that was previously impossible to test. This is really exciting! &lt;/p&gt;

&lt;p&gt;What was added: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Refund requests &lt;/li&gt;
&lt;li&gt;Offer codes&lt;/li&gt;
&lt;li&gt;Price increases &lt;/li&gt;
&lt;li&gt;Billing retry and a grace period &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Refund requests
&lt;/h3&gt;

&lt;p&gt;As you might guess, the option to cancel the subscription directly in the app was released. It means that you can test this functionality for your app. &lt;/p&gt;

&lt;p&gt;To show the corresponding screen you need to call just one function: &lt;a href="https://developer.apple.com/documentation/swiftui/view/refundrequestsheet(for:ispresented:ondismiss:)"&gt;refundRequestSheet(for:isPresented:onDismiss:)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The refund request sheet will appear above the view. It will include the list of refund reasons, and this list corresponds to &lt;a href="https://developer.apple.com/documentation/appstoreserverapi/revocationreason"&gt;revocationReason&lt;/a&gt; property in the App Store Server API.&lt;/p&gt;

&lt;p&gt;It looks like this: &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QXfnTrVr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w9uu1m75vosgktcq8kqb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QXfnTrVr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w9uu1m75vosgktcq8kqb.png" alt="Request Refund" width="758" height="1474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we are speaking about the Product in App Store, you should remember that refunds can take some time to process; however, when testing with Xcode or Sandbox, a refund request will immediately refund the transaction. You could see this refunded transaction in the Transaction Inspector. You’ll see both the refund reason and the refund date there. &lt;/p&gt;

&lt;p&gt;Moreover, you can test the refund directly in the Transaction Manager by clicking the “Refund purchases”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ulpwyd1F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zagrxrlzaz8n7sfac30s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ulpwyd1F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zagrxrlzaz8n7sfac30s.png" alt="Refund Purchases" width="880" height="629"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;We’ve described how to test refunds, but how does it look from the code perspective?&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://developer.apple.com/documentation/storekit/transaction"&gt;Transaction&lt;/a&gt; object that is received from StoreKit consists of &lt;a href="https://developer.apple.com/documentation/storekit/transaction/3803221-revocationdate"&gt;revocationDate&lt;/a&gt; and &lt;a href="https://developer.apple.com/documentation/storekit/transaction/3803222-revocationreason"&gt;revocationReason&lt;/a&gt; properties. This will help you detect the refunded transactions. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NUnZahfW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/du62imvcav59vxrse229.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NUnZahfW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/du62imvcav59vxrse229.png" alt="Refunds emit" width="880" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For iOS and iPadOS the testing through Xcode iIs available starting version 15.2, through Sandbox – starting 15.0. MasOS in Xcode – 12.1, Sandbox – 12.0. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gjB2Xkt1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vk3pufe2upf9pwkt6air.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gjB2Xkt1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vk3pufe2upf9pwkt6air.png" alt="Refund request" width="880" height="586"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Offer codes
&lt;/h3&gt;

&lt;p&gt;In the following example, let’s test offer codes redeem flow with the local configuration file. If you already have offer codes in App Store Connect, and you use synced StoreKit.configuration file, you’ll see these codes automatically in Xcode. &lt;/p&gt;

&lt;p&gt;For our case: go to the local file, click the “plus” in “Offer Codes”, configure Offer Code, and click Save.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y8-WOKrb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7by5hty5ymsyia3o0zlc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y8-WOKrb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7by5hty5ymsyia3o0zlc.png" alt="Offer Codes" width="880" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once configured, it should be tested. The code implementation is simple in the case of refund requests. You just need to call the function: &lt;a href="https://developer.apple.com/documentation/swiftui/view/offercoderedemption(ispresented:oncompletion:)"&gt;offerCodeRedemption(isPresented:onCompletion:)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you should see the redeem offer code sheet above your app. Only the users of your production app should enter offer code in this field. While testing with Xcode, you won’t need to enter offer code in this info, but rather only to choose the offer code from the list (as all this data is kept in StoreKit.configuration file).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8Mjq7v0Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oar01x3sa7173px1wsaq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8Mjq7v0Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oar01x3sa7173px1wsaq.png" alt="Redeem code" width="638" height="1270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s incredibly easy: choose the offer code, confirm the operation – and profit!&lt;/p&gt;

&lt;p&gt;Once the offer code is successfully processed, you’ll see this transaction in Transaction Manager; all the details will be available in the new Transaction Inspector. &lt;/p&gt;

&lt;p&gt;Let me skip the part where I explain the logic of managing this transaction, as it is quite straightforward. The only difference from managing the ordinary transaction is that you should check a few additional properties. &lt;/p&gt;

&lt;p&gt;While managing &lt;a href="https://developer.apple.com/documentation/storekit/transaction"&gt;transactions&lt;/a&gt; you can check the &lt;a href="https://developer.apple.com/documentation/storekit/transaction/3822313-offertype"&gt;offerType&lt;/a&gt; property to see if there is an offer applied to the current transaction. While handling &lt;a href="https://developer.apple.com/documentation/storekit/product/subscriptioninfo/renewalinfo"&gt;renewalInfo&lt;/a&gt;, use its property &lt;a href="https://dev.toofferType"&gt;offerType&lt;/a&gt; to see what kind of offer will be present in the next renewal.&lt;/p&gt;

&lt;p&gt;Offer codes are an incredibly valuable tool for working with users in your app. There are so many things you can achieve, from user acquisition to winning churning customers and support/loyalty programs. So it’s incredibly valuable that we now have a ability to test it.&lt;/p&gt;

&lt;p&gt;Just to remind you, this option didn’t previously exist, even in the Sandbox!&lt;/p&gt;

&lt;p&gt;Everything described above is available starting Xcode 13.3 and higher and iOS/iPadOS 15.4 and higher. &lt;/p&gt;

&lt;h3&gt;
  
  
  Price increases
&lt;/h3&gt;

&lt;p&gt;As you might know from our &lt;a href="https://qonversion.io/blog/whats-new-with-in-app-purchases-wwdc-2022/"&gt;previous overview&lt;/a&gt; of WWDC22, StoreKit Messages are aimed to be used to display messages from the App Store API to users. One example of such messages is a change in the app’s price. Let’s take a closer look at how we can test this scenario. &lt;/p&gt;

&lt;p&gt;First, change the price of the Product in the StoreKit.configuration file in Xcode. Alternatively, you can choose the needed transaction in the Transaction manager and click the “Request Price Increase Consent”.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UWphMRdE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/koqla7llt95sw06i5crd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UWphMRdE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/koqla7llt95sw06i5crd.png" alt="Price increases" width="880" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can skip the flow of how to display the StoreKit Messages, but in this case the message will appear automatically over your app. If you’d like to avoid interrupting your user’s flow and do not want to affect user experience, you should implement the StoreKit Messages sequence the same way that you handle the transactions in StoreKit. Find these details &lt;a href="https://developer.apple.com/documentation/storekit/message/3954510-messages"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What is important is that this price-changing screen might be shown to your users several times until they consent to the new terms. In Xcode, you can manually call this screen only the needed number of times (read above). It is important to remember that the users might consent outside of the app – for instance, via email. &lt;/p&gt;

&lt;p&gt;In Xcode, you can test this consent screen display scenario just by clicking the corresponding buttons in Transaction Manager.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f05K1M65--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xpuvia7vee4vx9lymt5t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f05K1M65--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xpuvia7vee4vx9lymt5t.png" alt="Transaction manager" width="880" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s take a look at the code side. As you work with the purchases in your app, you probably are already implemented &lt;a href="https://developer.apple.com/documentation/storekit/product/subscriptioninfo/status/3851115-updates"&gt;Statuses.updates sequence&lt;/a&gt;. The only thing that you should do now is to check &lt;a href="https://developer.apple.com/documentation/storekit/product/subscriptioninfo/renewalinfo"&gt;renewalInfo&lt;/a&gt;  =&amp;gt; &lt;a href="https://developer.apple.com/documentation/storekit/product/subscriptioninfo/renewalinfo/3822292-priceincreasestatus"&gt;priceIncreaseStatus&lt;/a&gt; property to receive the needed state. Check the &lt;a href="https://developer.apple.com/documentation/storekit/product/subscriptioninfo/renewalinfo/3749499-expirationreason"&gt;expirationReason&lt;/a&gt; property to detect if the customer canceled the subscription after the price changed. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--H8SldjJ_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s0vl5ztc6em4bxvu9w24.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--H8SldjJ_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s0vl5ztc6em4bxvu9w24.png" alt="Price increase changes emit" width="880" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All this is unlocked by starting the following versions of OS: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SKuF2IV7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ciqlyp7svpyl4ft482z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SKuF2IV7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ciqlyp7svpyl4ft482z.png" alt="Price increase testing" width="880" height="677"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Please consider that StoreKit Message testing on price changing is available just for iOS/iPadOS 15.4 version and higher.&lt;/p&gt;

&lt;h3&gt;
  
  
  Billing retry and grace period
&lt;/h3&gt;

&lt;p&gt;Billing retry is where an error occurred while trying to renew a subscription, such as if a credit card is expired. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pzeZGR-u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yvyctok13okvyqxruibo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pzeZGR-u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yvyctok13okvyqxruibo.png" alt="Billing retry" width="880" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, App Store will attempt to fix the billing issue to renew the subscription.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PkC5SVMc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tmm6o5fh431hcpzs9oxd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PkC5SVMc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tmm6o5fh431hcpzs9oxd.png" alt="Billing retry" width="880" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can enable a grace period that gives your customer the ability to use your subscription for a limited period of time at the beginning of the billing retry state. In other words, this is a period for the user to solve billing retry issues, pay for the subscription, and continue to experience the app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oKvT82Eb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zz07cddi91t4m3hjjwn6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oKvT82Eb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zz07cddi91t4m3hjjwn6.png" alt="Billing retry" width="880" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s take a look at how to test this case in Xcode. &lt;/p&gt;

&lt;p&gt;Choose your StoreKit.configuration file, then Editor Menu =&amp;gt; Enable Billing Retry on Renewal. If you’d like to test the grace period, you can refer to the next point in the same menu.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--c-YWQvbB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2qqwvpivqbsqhydrtzt3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--c-YWQvbB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2qqwvpivqbsqhydrtzt3.png" alt="Renewal" width="880" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For further convenience, you can speed up the subscription renewal rate from the same menu.&lt;/p&gt;

&lt;p&gt;The next steps are straightforward. Let’s subscribe to the app then wait for the renewal period time (that you choose in the Subscription Renewal Rate menu). At the moment of renewal, you’ll see the new transaction with Billing Retry in the Transaction Manager. The Transaction Inspector will show that the transaction is in the grace period. Once the grace period expires, the transaction appears in the standard billing retry state. You can resolve the billing issue by clicking the corresponding button in Transaction Manager. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LuBkpmku--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5jpw0l9i4bdslym8gqlj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LuBkpmku--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5jpw0l9i4bdslym8gqlj.png" alt="Transaction manager" width="880" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Handling billing retries and grace periods is key to retaining subscribers by reducing involuntary churn.&lt;/p&gt;

&lt;p&gt;Now it’s time to explore how you can handle this in the code.&lt;/p&gt;

&lt;p&gt;As the billing retry and grace period states change, the &lt;a href="https://developer.apple.com/documentation/storekit/product/subscriptioninfo/status/3851115-updates"&gt;Status.updates sequence&lt;/a&gt; will emit a new value.&lt;/p&gt;

&lt;p&gt;All that you should do is check, using the &lt;a href="https://developer.apple.com/documentation/storekit/product/subscriptioninfo/renewalinfo/3749500-graceperiodexpirationdate"&gt;renewalInfo.GracePeriodExpirationDate&lt;/a&gt; property, whether your user is in the grace period. If so, you should grant him access as if the subscription were active. To detect whether the user is in a billing retry state, check the &lt;a href="https://developer.apple.com/documentation/storekit/product/subscriptioninfo/renewalinfo/3749502-isinbillingretry"&gt;renewalInfo.IsInBillingRetry&lt;/a&gt; property.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_9ASJRSk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p3m9pzpj8nmsbx9e6w8e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_9ASJRSk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p3m9pzpj8nmsbx9e6w8e.png" alt="Billing retry" width="880" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both of these states can be checked with &lt;a href="https://developer.apple.com/documentation/storekit/product/subscriptioninfo/status/3749528-state"&gt;state&lt;/a&gt; property in  &lt;a href="https://developer.apple.com/documentation/storekit/product/subscriptioninfo/status"&gt;Product.SubscriptionInfo.Status&lt;/a&gt; you’d like, you can redirect your user to the App Store to fix the billing issues.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qfomPBJd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/al6e2f8jky5e5wo8ns0n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qfomPBJd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/al6e2f8jky5e5wo8ns0n.png" alt="Billing retry" width="880" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are using any &lt;a href="https://developer.apple.com/documentation/storekit/transaction/3851204-currententitlements"&gt;currentEntitlement API&lt;/a&gt;, you’ll receive the transactions for expired subscriptions while they’re in the grace period.&lt;/p&gt;

&lt;p&gt;In which versions is this feature available? Check the following: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AWjJsqRO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mpbcbh2dlu4bjlkjg10q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AWjJsqRO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mpbcbh2dlu4bjlkjg10q.png" alt="Billing retry testing" width="880" height="709"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So these are the exciting updates with Xcode testing! These will definitely improve the developers’ experience while working with in-app purchases. &lt;/p&gt;

&lt;h2&gt;
  
  
  Sandbox updates
&lt;/h2&gt;

&lt;p&gt;Last but not least – the updates related to Sandbox.&lt;/p&gt;

&lt;p&gt;What we’ll explore: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sandbox Apple ID creation&lt;/li&gt;
&lt;li&gt;App Store Connect API&lt;/li&gt;
&lt;li&gt;Billing failure simulation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sandbox Apple ID creation
&lt;/h3&gt;

&lt;p&gt;To unlock the ability to test in Sandbox, you have to create a testing user.&lt;/p&gt;

&lt;p&gt;There are several pieces of good news. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The number of fields that you should fill while creating the user significantly decreased. &lt;/li&gt;
&lt;li&gt;You can use “+” symbol in email to prevent you from creating a new email for every user. For instance, “&lt;a href="mailto:your_super_test+sandbox@iclod.com"&gt;your_super_test+sandbox@iclod.com&lt;/a&gt;”&lt;/li&gt;
&lt;li&gt;Added in-line suggestions for passwords&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gAdsONh9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/00hc2jebbaqxkkw4wt11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gAdsONh9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/00hc2jebbaqxkkw4wt11.png" alt="New tester" width="880" height="636"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  App Store Connect API
&lt;/h3&gt;

&lt;p&gt;For the last few years, Apple engineers have added a lot of features to Sandbox per developers’ requests. The changing of the Sandbox account region and clearing purchase history is one of these. &lt;/p&gt;

&lt;p&gt;Most of these features are available in App Store Connect or directly on the device on the Sandbox Manage Subscriptions page.&lt;/p&gt;

&lt;p&gt;Later this year, the following Sandbox features will be added to App Store Connect API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query for Sandbox Apple IDs&lt;/li&gt;
&lt;li&gt;Clear in-app purchase history&lt;/li&gt;
&lt;li&gt;Set interrupted purchase state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These features will improve the speed of subscription testing with Sandbox accounts and add possibility of automated testing in the basic scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Billing failure simulation
&lt;/h3&gt;

&lt;p&gt;Since launching in 2019, Billing Grace Period allowed developers to recover 300 million days of paid service to their customers. Soon, there will be the capability to use a new Sandbox Account Settings page to: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enable billing failure simulation for your account&lt;/li&gt;
&lt;li&gt;test foreground and background subscription failures&lt;/li&gt;
&lt;li&gt;verify subscription status with &lt;a href="https://developer.apple.com/documentation/appstorereceipts/verifyreceipt"&gt;verifyReceipt&lt;/a&gt;, &lt;a href="https://developer.apple.com/documentation/appstoreserverapi"&gt;App Store Server API&lt;/a&gt; and &lt;a href="https://developer.apple.com/documentation/appstoreservernotifications/app_store_server_notifications_v2"&gt;App Store Server Notifications v2 in Sandbox&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With these testing scenarios, Apple &lt;a href="https://developer.apple.com/videos/play/wwdc2022/10039/"&gt;explores&lt;/a&gt; the statuses that will affect App Store Notifications V2. This is a big topic to cover, so stay tuned for other articles! &lt;/p&gt;

&lt;p&gt;We know a thing or two about in-app purchases as &lt;a href="https://qonversion.io/"&gt;Qonversion&lt;/a&gt; provides a complete cross-platform infrastructure that allows you to create and restore purchases, validate receipts, and provide your app with an accurate subscription status without the need to build your server. So, If you’d like to learn more about it or have any questions, feel free to ping me in the comments. &lt;/p&gt;

&lt;p&gt;If you'd like to learn more on &lt;a href="https://qonversion.io/blog/handling-storekit-errors/"&gt;how to handle StoreKit errors&lt;/a&gt;, please follow our guide. Also, recently I've created an article on how to solve &lt;a href="https://qonversion.io/blog/skerrordomain-code-0/"&gt;SKErrorDomain 0&lt;/a&gt;, feel free to check!  &lt;/p&gt;

</description>
      <category>storekit</category>
      <category>ios</category>
      <category>wwdc22</category>
      <category>testing</category>
    </item>
    <item>
      <title>WWDC22 overview: how to integrate and migrate in-app purchases to App Store Server API</title>
      <dc:creator>Surik Sarkisyan</dc:creator>
      <pubDate>Fri, 10 Jun 2022 10:47:11 +0000</pubDate>
      <link>https://dev.to/qonversion/wwdc22-overview-how-to-integrate-and-migrate-in-app-purchases-to-app-store-server-api-1mcc</link>
      <guid>https://dev.to/qonversion/wwdc22-overview-how-to-integrate-and-migrate-in-app-purchases-to-app-store-server-api-1mcc</guid>
      <description>&lt;p&gt;Hey everyone, today we’re going to continue our WWDC22 blog post series on what’s new with in-app purchases. We’ve already covered &lt;a href="https://qonversion.io/blog/whats-new-with-in-app-purchases-wwdc-2022/"&gt;some of the updates&lt;/a&gt;: enhancements to StoreKit 2 and App Store Server API. &lt;/p&gt;

&lt;p&gt;Today, we’ll focus on the next updates that were announced during the session &lt;a href="https://developer.apple.com/videos/play/wwdc2022/10040/"&gt;Explore in-app purchase integration and migration&lt;/a&gt;. The session is divided into two chapters – App Store Server API and App Store Server Notifications Version 2.&lt;/p&gt;

&lt;p&gt;In this article, I’ll mostly refer to the first part of the video. We’ll talk about App Store Server API – powerful, secure, and with efficient server-to-server endpoints, then we’ll explain how to migrate to it.&lt;/p&gt;

&lt;p&gt;The main idea behind this API is pretty clear: to give you the all data you need about in-app purchases. &lt;/p&gt;

&lt;p&gt;App Store API was introduced on WWDC 2021. These are two great sessions that I highly recommend to watch; however, they won’t affect your understanding of today’s topic. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2021/10174"&gt;Manage in-app purchases on your server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2021/10175"&gt;Support customers and handle refunds&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What we will cover: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to use the App Store Server API&lt;/li&gt;
&lt;li&gt;How to sign JSON Web Tokens&lt;/li&gt;
&lt;li&gt;How to verify signed transactions&lt;/li&gt;
&lt;li&gt;How to migrate from verifyReceipt 
So, let’s get going!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Using the App Store Server API
&lt;/h2&gt;

&lt;p&gt;In this part, we’ll cover the cases of Using the App Store Server API. Additionally, I’ll cover how to use the new API with StoreKit in different scenarios: when you need to support Original StoreKit, StoreKit 2, and both of it – for instance, if you decided to keep supporting StoreKit for users with previous iOS versions and add StoreKit 2 support for those who use updated versions of Apple OS. &lt;/p&gt;

&lt;h3&gt;
  
  
  Which endpoints are available with the new API?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://developer.apple.com/documentation/appstoreserverapi/get_all_subscription_statuses"&gt;GET /inApps/v1/subscriptions/{originalTransactionId}&lt;/a&gt; – get the statuses for all of a customer’s auto-renewable subscriptions in your app.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.apple.com/documentation/appstoreserverapi/get_transaction_history"&gt;GET /inApps/v1/history/{originalTransactionId}&lt;/a&gt; – get a customer’s in-app purchase transaction history for your app.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.apple.com/documentation/appstoreserverapi/send_consumption_information"&gt;PUT /inApps/v1/transactions/consumption/{originalTransactionId}&lt;/a&gt; – send consumption information about a consumable in-app purchase to the App Store after your server receives a consumption request notification.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.apple.com/documentation/appstoreserverapi/get_refund_history"&gt;GET /inApps/v1/refund/lookup/{originalTransactionId}&lt;/a&gt; – get a list of all refunded in-app purchases in your app for a customer.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.apple.com/documentation/appstoreserverapi/extend_a_subscription_renewal_date"&gt;PUT /inApps/v1/subscriptions/extend/{originalTransactionId}&lt;/a&gt; – extend the renewal date of a customer’s active subscription using the original transaction identifier.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.apple.com/documentation/appstoreserverapi/look_up_order_id"&gt;GET /inApps/v1/lookup/{orderId}&lt;/a&gt; – get a customer’s in-app purchases from a receipt using the order ID.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first 5 requests require &lt;code&gt;originalTransactionid&lt;/code&gt; as an input parameter, which allows you to easily use these requests just having &lt;code&gt;originalTransactionId&lt;/code&gt;. You can receive this parameter from receipts, signed transactions, signed auto-renewals, and notifications.&lt;/p&gt;

&lt;p&gt;The 6th request requires &lt;code&gt;orderId&lt;/code&gt; as an input parameter. But what is that parameter used for?&lt;/p&gt;

&lt;p&gt;The main idea is to support and interact with your clients. When the customer makes a purchase, he receives an email with the data about the completed purchase. This email includes some details, and the &lt;code&gt;orderId&lt;/code&gt; is one of them. This data could be received as well from a purchase history on the App Store. &lt;/p&gt;

&lt;p&gt;Thus, when the user contacts your support team and sends a support query, he shares the &lt;code&gt;orderId&lt;/code&gt;, and you receive the needed data through the &lt;a href="https://developer.apple.com/documentation/appstoreserverapi/look_up_order_id"&gt;Look Up Order ID endpoint&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;It’s worth mentioning that Apple announced 3 more endpoints as well, but as we’ll explore these and other Apple notifications in future articles. For now, let’s just take a quick look and move on. &lt;/p&gt;

&lt;p&gt;Here they are: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://developer.apple.com/documentation/appstoreserverapi/request_a_test_notification"&gt;POST /inApps/v1/notifications/test&lt;/a&gt; – Ask App Store Server Notifications to send a test notification to your server.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.apple.com/documentation/appstoreserverapi/get_test_notification_status"&gt;GET /inApps/v1/notifications/test/{testNotificationToken}&lt;/a&gt; – Check the status of the test App Store server notification sent to your server.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.apple.com/documentation/appstoreserverapi/get_notification_history"&gt;GET /inApps/v1/notifications/history&lt;/a&gt; – Get a list of notifications that the App Store server attempted to send to your server.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  How to get &lt;code&gt;originalTransactionIds&lt;/code&gt; with the Original StoreKit?
&lt;/h3&gt;

&lt;p&gt;When you call the endpoint &lt;a href="https://developer.apple.com/documentation/appstorereceipts/verifyreceipt"&gt;verifyReceipt&lt;/a&gt;, you can see that in &lt;a href="https://developer.apple.com/documentation/appstorereceipts/responsebody"&gt;responseBody&lt;/a&gt; =&amp;gt; &lt;a href="https://developer.apple.com/documentation/appstorereceipts/responsebody/receipt/in_app"&gt;responseBody.Receipt.In_app&lt;/a&gt; field you can find &lt;code&gt;originalTransactionId&lt;/code&gt; for each user’s purchase. The same can be found in &lt;a href="https://developer.apple.com/documentation/appstorereceipts/responsebody/latest_receipt_info"&gt;responseBody.Latest_receipt_info&lt;/a&gt; and &lt;a href="https://developer.apple.com/documentation/appstorereceipts/responsebody/pending_renewal_info"&gt;responseBody.Pending_renewal_info&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Let’s take a look at what this looks like: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rdYIweOX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kkmb6cwtyxri2eewzkya.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rdYIweOX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kkmb6cwtyxri2eewzkya.jpeg" alt="Image description" width="880" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With knowledge of how to get the &lt;code&gt;originalTransactionId&lt;/code&gt; from Original StoreKit transactions, let’s explore the whole flow between a customer, the App Store Server, and your server: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Obtain the app receipt on our server&lt;/li&gt;
&lt;li&gt;Take the app receipt and call &lt;a href="https://developer.apple.com/documentation/appstorereceipts/verifyreceipt"&gt;verifyReceipt&lt;/a&gt; with it from your server.&lt;/li&gt;
&lt;li&gt;Get the decoded receipt in a response.&lt;/li&gt;
&lt;li&gt;Get all of the &lt;code&gt;originalTransactionIds&lt;/code&gt; from the decoded receipt in precisely the same ways I previously showed.&lt;/li&gt;
&lt;li&gt;Call &lt;a href="https://developer.apple.com/documentation/appstoreserverapi/get_transaction_history"&gt;GET /inApps/v1/history/{originalTransactionId}&lt;/a&gt; with any one of the gathered &lt;code&gt;originalTransactionIds&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Handle history of transactions for this user in response. These transactions include non-consumables, refunded consumables, non-renewing subscriptions, and auto-renewing subscriptions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then, if you want to get the latest signed transaction and signed renewal information for a specific subscription, call &lt;a href="https://developer.apple.com/documentation/appstoreserverapi/get_all_subscription_statuses"&gt;GET /inApps/v1/subscriptions/{originalTransactionId}&lt;/a&gt; with the corresponding &lt;code&gt;originalTransactionId&lt;/code&gt;. As a result, you’ll receive the list of all signed transactions and signed renewals, which corresponds to &lt;code&gt;originalTransactionId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IsZ6tunp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ocf26rw1fr9i0hcskc47.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IsZ6tunp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ocf26rw1fr9i0hcskc47.jpeg" alt="Image description" width="880" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How to get &lt;code&gt;originalTransactionIds&lt;/code&gt; with the StoreKit 2?
&lt;/h3&gt;

&lt;p&gt;On the client you can get the &lt;code&gt;originalTransactionIds&lt;/code&gt; with the following steps: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get &lt;a href="https://developer.apple.com/documentation/storekit/transaction"&gt;signed transaction&lt;/a&gt; from the devices that are on iOS 15 and later&lt;/li&gt;
&lt;li&gt;Get the &lt;a href="https://developer.apple.com/documentation/storekit/transaction/3749703-originalid"&gt;originalID&lt;/a&gt; property from this transaction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In terms of the API side – you’ll receive the &lt;code&gt;originalTransactionIds&lt;/code&gt; on the top-level field, as described in the screenshot below. Here you’ll see an example of a signed JWS transaction, which is the data type you receive in signed transactions and in signed renewals from the App Store Server API and App Store Server Notifications.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Hgd1Hfk3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s005c0owh107b51xqgqv.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Hgd1Hfk3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s005c0owh107b51xqgqv.jpeg" alt="Image description" width="880" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, let’s proceed with the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get the transaction on a user’s device &lt;/li&gt;
&lt;li&gt;Send the transaction to your server &lt;/li&gt;
&lt;li&gt;When you need to perform an operation on a subscription, such as extending the renewal date of a subscription, you can use the &lt;code&gt;originalTransactionId&lt;/code&gt; from the signed transaction to call &lt;a href="https://developer.apple.com/documentation/appstoreserverapi/extend_a_subscription_renewal_date"&gt;PUT /inApps/v1/subscriptions/extend/{originalTransactionId}&lt;/a&gt; endpoint and get back the data that you need.
The screenshot:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GcOLrYhu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lex83clbjik1h2y5e5y4.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GcOLrYhu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lex83clbjik1h2y5e5y4.jpeg" alt="Image description" width="880" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How to handle &lt;code&gt;originalTransactionIds&lt;/code&gt; with both versions of StoreKit?
&lt;/h3&gt;

&lt;p&gt;So, we explored the ways to use the new API with the Original StoreKit and StoreKit2. &lt;/p&gt;

&lt;p&gt;For the cases when you have to support both versions of StoreKit, the advice is quite simple: just take all the advantages of the App Store API and use them regardless of the version of StoreKit that processed the purchase. &lt;/p&gt;

&lt;p&gt;To validate new purchases that were made with the Original StoreKit, the process is simple: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get the receipt from a customer &lt;/li&gt;
&lt;li&gt;Send this receipt to your server &lt;/li&gt;
&lt;li&gt;Call &lt;a href="https://developer.apple.com/documentation/appstorereceipts/verifyreceipt"&gt;verifyReceipt&lt;/a&gt; endpoint to receive decoded receipt &lt;/li&gt;
&lt;li&gt;Request the status of the purchase with &lt;a href="https://developer.apple.com/documentation/appstoreserverapi/get_all_subscription_statuses"&gt;GET /inApps/v1/subscriptions/{originalTransactionId}&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Get all signed transactions and renewals
It’s essentially the same as the process described before, except that, as you might notice, the history purchase step is skipped. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nkf54XjA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/02g8295kwmwweccqlfvs.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nkf54XjA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/02g8295kwmwweccqlfvs.jpeg" alt="Image description" width="880" height="449"&gt;&lt;/a&gt;&lt;br&gt;
That’s it for App Store API!&lt;/p&gt;
&lt;h2&gt;
  
  
  Signing JSON Web Tokens
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc7519"&gt;JSON Web Token&lt;/a&gt; is needed for Apple to authenticate you when calling the App Store Server API. This token should be used as an authorization header in each of your server’s requests.&lt;br&gt;
JWT consist of: header, payload, and the signature.&lt;/p&gt;
&lt;h3&gt;
  
  
  How to construct JSON Web Token for your app?
&lt;/h3&gt;

&lt;p&gt;On the picture below you can see the structure of the JWT, header and payload.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zksLe4hd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/edd9cwmv8t61jmi021gl.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zksLe4hd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/edd9cwmv8t61jmi021gl.jpeg" alt="Image description" width="880" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The token itself can be divided into three parts, separated by periods:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The base64 encoded header&lt;/li&gt;
&lt;li&gt;The base64 encoded payload&lt;/li&gt;
&lt;li&gt;The signature, which is composed of the base64 encoded header + base64 encoded payload, signed using your signing secret.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;
  
  
  Header
&lt;/h4&gt;

&lt;p&gt;The header consists of metadata that represents the information on how to sign your data. &lt;/p&gt;

&lt;p&gt;Pay attention to the key ID field (kid) – this is your private key ID from the App Store Connect. This key ID should correspond to the key that you use for JWT signature.&lt;/p&gt;
&lt;h4&gt;
  
  
  Payload
&lt;/h4&gt;

&lt;p&gt;Payload keeps all the data about your application. To learn more about this field please read the official &lt;a href="https://developer.apple.com/documentation/appstoreserverapi/generating_tokens_for_api_requests"&gt;documentation from Apple&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;For additional info on how to get your API key please read &lt;a href="https://developer.apple.com/documentation/appstoreserverapi/creating_api_keys_to_use_with_the_app_store_server_api"&gt;this article&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Once you receive all the data that you need for the JWT, you need to sign the JWT ​​using the certificate that corresponds to the key ID.&lt;/p&gt;

&lt;p&gt;Let’s take a closer look at an example (pseudocode). Please note that the final code will look different depending on the programming language, the chosen tools, and libraries with which you will implement it. But the logic behind remains the same.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import jwtLibrary;

var privateKey = readFile(“/path/to/private_key.key”)
var token = jwtLibrary.sign(payload, privateKey, header)
One your preferred library generates token, you can paste it into cURL that will get subscription statuses by `originalTransactionId`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This is an example:

curl -v -S -H 'Content-Type: application/json' \
    -H 'Accept: application/json' \
    -H 'Authorization: Bearer ${token}' \
    https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/${originalTransactionId}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don’t forget to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;replace ${token} with the parameter that you’ve generated in example above. &lt;/li&gt;
&lt;li&gt;replace ${originalTransactionId} – the Id, with which one you’d like to get the data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Verifying signed transactions
&lt;/h2&gt;

&lt;p&gt;Let’s discuss how to verify the transactions that you receive from Apple and that were signed by the App Store. &lt;/p&gt;

&lt;p&gt;Signed transactions are JSON and are cryptographically signed, so if someone would try to replace it between App Store and your server, you can easily detect it. &lt;/p&gt;

&lt;p&gt;These transactions are signed in the JWS format – Json Web Signature (do not confuse this with JWT, which was discussed above). By verifying this object you receive the data from the App Store and can ensure that it was not tampered by anyone. &lt;/p&gt;

&lt;p&gt;How can I verify a signed transaction?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Decode base64 header&lt;/li&gt;
&lt;li&gt;By &lt;code&gt;alg claim&lt;/code&gt; in this base64 header you could understand which algorithm you should use. It will be used for the JWS verification. &lt;/li&gt;
&lt;li&gt;Verify certificate chain in the x5c claim
Now, once we verified this data, we are on the safe side – no one tampered our data. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can learn more about JWS &lt;a href="https://datatracker.ietf.org/doc/html/rfc7515"&gt;here&lt;/a&gt;. Also, please read the App Store Server API documentation to learn more about&lt;a href="https://developer.apple.com/documentation/appstoreserverapi/jwstransaction"&gt; JWSTransaction&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What is the x5c chain? &lt;/p&gt;

&lt;p&gt;This is a chain of certificates, the successful verification of which means that the data can be trusted and is signed by Apple. &lt;/p&gt;

&lt;p&gt;What is important for the certificate chain is the order. At the beginning you’ll get the root certificate. The root certificate may be followed by additional certificates from the chain. Each following certificate is signed by the previous one. The last certificate in this chain is referred to as the leaf certificate. The first certificate is referred to as the root certificate, and is self-signed. &lt;/p&gt;

&lt;p&gt;This certificate should match to the root certificate you obtain from the Apple’s Certificate Authority. If it does not, then the verification chain will be defined as failed. &lt;/p&gt;

&lt;p&gt;The last certificate (leaf certificate) is used for JWS signing. &lt;/p&gt;

&lt;p&gt;Let’s take a look at the JWS header: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zqbY5saQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i8urlhuimrrd8fjsynny.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zqbY5saQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i8urlhuimrrd8fjsynny.jpeg" alt="Image description" width="880" height="233"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the beginning of the first line, in the &lt;code&gt;alg&lt;/code&gt; field, there is the name of the algorithm that is used for the JWS signing. Next, in the &lt;code&gt;x5c&lt;/code&gt;, there is a chain of certificates listed in order. &lt;/p&gt;

&lt;p&gt;Let’s look a bit more closely at what x5c certificate chain generation looks like. &lt;/p&gt;

&lt;p&gt;At the beginning, we take a root certificate from the Apple’s Certificate Authority. Next, we sign the intermediate certificate. There might be several certificates in this chain, and as we discussed above, each following certificate is signed by previous one. In the example below we consider the one intermediate certificate. Then, this intermediate certificate signs the leaf certificate. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--AbreMRtF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ldm8eunlulb9o8bfdix2.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AbreMRtF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ldm8eunlulb9o8bfdix2.jpeg" alt="Image description" width="880" height="526"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, we’ve described the way this chain generates. Now it’s time to move on to the next chapter – how you can verify it. &lt;/p&gt;

&lt;p&gt;This part is simple – you just need to reverse the process.&lt;/p&gt;

&lt;p&gt;First, make sure that the leaf certificate is signed by intermediate one. Then, that intermediate certificate should be signed by the root certificate. And the root certificate should match the one one of Apple’s Certificate Authorities. If all steps are successful, the chain is verified and counted as valid. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qHAOKwxh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m2n7rq7z0yu8mlxqp7u4.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qHAOKwxh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m2n7rq7z0yu8mlxqp7u4.jpeg" alt="Image description" width="880" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s discuss a specific method of chain certificate verification. &lt;/p&gt;

&lt;p&gt;This is the command fot x5c chain using OpenSSL verification: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;openssl verify -trusted AppleRootCA-G3.pem -untrusted AppleWWDRCAG6.pem leaf.pem&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The command &lt;code&gt;verify&lt;/code&gt; initiates the process of certificate verification. &lt;/p&gt;

&lt;p&gt;With &lt;code&gt;-trusted&lt;/code&gt; we send the certificate that we trust and that will be used for the verification of the following certificates. This should be root certificate that was obtained from Apple’s Certificate Authority.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;-untrusted&lt;/code&gt; we send a certificate that wasn’t verified yet, but that we’d like to verify. &lt;/p&gt;

&lt;p&gt;As a first parameter we use the WWDR certificate that received from the Apple’s Certificate Authority and signed by root certificate. This should match to the second certificate from the x5c chain. The last parameter is a leaf certificate that was signed by the previous certificate. In case of successful verification, you’ll receive a success code, and in case of failed verification, you’ll receive an error code. If verification is unsuccessful, the data shouldn’t be used. &lt;/p&gt;

&lt;p&gt;More information about the &lt;code&gt;verify&lt;/code&gt; command in &lt;code&gt;openssl&lt;/code&gt; is &lt;a href="https://www.openssl.org/docs/man3.0/man1/openssl-verify.html"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The information on &lt;code&gt;openssl&lt;/code&gt; is &lt;a href="https://github.com/openssl/openssl"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migration from verifyReceipt to App Store Server API
&lt;/h3&gt;

&lt;p&gt;In this part, we’ll explore the several corner cases. &lt;/p&gt;

&lt;h4&gt;
  
  
  How to check the subscription status of the particular user
&lt;/h4&gt;

&lt;p&gt;Previously, you had to call &lt;a href="https://developer.apple.com/documentation/appstorereceipts/verifyreceipt"&gt;verifyReceipt&lt;/a&gt; and define the status by checking several fields from the response. Now, with App Store Server API you can call &lt;a href="https://developer.apple.com/documentation/appstoreserverapi/get_all_subscription_statuses"&gt;GET /inApps/v1/subscriptions/{originalTransactionId}&lt;/a&gt; and receive all the needed data, such as subscription status, renewal info and other pieces of data in one response. &lt;/p&gt;

&lt;h4&gt;
  
  
  How to receive the latest transactions of the user
&lt;/h4&gt;

&lt;p&gt;The next case described is when you’d like to get the latest transactions of the user to check what he purchased, whether the subscription is auto renewed, or whether there were changes like upgrades, downgrades, or plan switches.&lt;/p&gt;

&lt;p&gt;As in the previous example, with the previous API version you had to call &lt;a href="https://developer.apple.com/documentation/appstorereceipts/verifyreceipt"&gt;verifyReceipt&lt;/a&gt; and get the data from the &lt;a href="https://developer.apple.com/documentation/appstorereceipts/responsebody/receipt/in_app"&gt;responseBody.Receipt.In_app&lt;/a&gt; and &lt;a href="https://developer.apple.com/documentation/appstorereceipts/responsebody/latest_receipt_info"&gt;responseBody.Latest_receipt_info fields&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;With the new App Store Server API you can refer to the &lt;a href="https://developer.apple.com/documentation/appstoreserverapi/get_transaction_history"&gt;GET /inApps/v1/history/{originalTransactionId}&lt;/a&gt; endpoint that covers all needed data. &lt;br&gt;
And last but not least – the new &lt;a href="https://developer.apple.com/documentation/storekit/transaction/3749684-appaccounttoken"&gt;appAccountToken&lt;/a&gt; field that fetches UUID. We’ve covered this field in &lt;a href="https://qonversion.io/blog/whats-new-with-in-app-purchases-wwdc-2022/"&gt;our previous article&lt;/a&gt;, but just to remind you: this field lets you add the unique ID of the users from your system just to connect your user with their purchases. In the previous API version there was an analogue of this field –  &lt;a href="https://developer.apple.com/documentation/storekit/skmutablepayment/1506088-applicationusername"&gt;applicationUsername&lt;/a&gt; – that fetched the string, but the official documentation recommended sending UUID into this field. And once you send the UUID format string into this field, you’ll see this parameter automatically in &lt;code&gt;appAccountToken&lt;/code&gt;. The needed value will appear in StoreKit Transaction, App Store API, and App Store Server Notifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we explored the new capabilities of App Store Server API, how to integrate and migrate in-app subscriptions into API, and how to sign JSON Web Tokens and verify signed transactions.&lt;/p&gt;

&lt;p&gt;If you still have any questions about the logic behind these updates, please feel free to &lt;a href="https://docs.google.com/forms/d/e/1FAIpQLSckDhZHw7fOn3H67Qpp_iR3f8PqVfVpnaUthzVHiTc_Gwvm5w/viewform"&gt;reach out to us&lt;/a&gt;. &lt;a href="https://qonversion.io/"&gt;Qonversion&lt;/a&gt; provides a complete cross-platform infrastructure that allows you to make and restore purchases, validate receipts, and provide your app with an accurate subscription status without the need to build your server. &lt;/p&gt;

&lt;p&gt;If you'd like to learn more on &lt;a href="https://qonversion.io/blog/handling-storekit-errors/"&gt;how to handle StoreKit errors&lt;/a&gt;, please follow our guide. Also, recently I've created an article on how to solve &lt;a href="https://qonversion.io/blog/skerrordomain-code-0/"&gt;SKDomainError 0&lt;/a&gt;, feel free to check! &lt;/p&gt;

</description>
      <category>mobile</category>
      <category>ios</category>
      <category>wwdc</category>
      <category>api</category>
    </item>
    <item>
      <title>What’s new with in-app purchases: WWDC 2022 overview</title>
      <dc:creator>Surik Sarkisyan</dc:creator>
      <pubDate>Thu, 09 Jun 2022 13:31:14 +0000</pubDate>
      <link>https://dev.to/qonversion/whats-new-with-in-app-purchases-wwdc-2022-overview-2bpj</link>
      <guid>https://dev.to/qonversion/whats-new-with-in-app-purchases-wwdc-2022-overview-2bpj</guid>
      <description>&lt;p&gt;This Tuesday, during &lt;a href="https://developer.apple.com/wwdc22/"&gt;WWDC 2022&lt;/a&gt;, Apple released the latest updates for in-app purchases. A lot of great updates are still coming, but today we’ll walk you through the improvements that have already been covered: enhancements to StoreKit 2 and App Store Server API. &lt;/p&gt;

&lt;p&gt;So, let’s get started. &lt;/p&gt;

&lt;h2&gt;
  
  
  New App Transaction API is released
&lt;/h2&gt;

&lt;p&gt;Apple has released the new API to verify the purchases of the app. The new API aims to mitigate (or totally prevent) fraud and add more flexibility to the ways you can monetise the app and grant users access.&lt;/p&gt;

&lt;p&gt;The new App Transaction API presents the signed information about the purchase and the device with which the purchase was made.&lt;/p&gt;

&lt;p&gt;What you should know about the new App Transaction API&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;- It’s signed using JWS signature&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;- It replaces the app details of the app receipt from the original StoreKit API&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;- StoreKit validates purchases automatically&lt;/strong&gt;&lt;br&gt;
You still can create your own receipt validation flow, but you’ll need to validate a JWS signature. As Apple highlights, the process is described in the official documentation, so you can easily handle it on your own. &lt;br&gt;
&lt;strong&gt;- StoreKit automatically refreshes transactions, but in rare cases users can do this manually.&lt;/strong&gt;&lt;br&gt;
To provide this functionality, you need to create UI capabilities within the app so users can refresh the app transaction themselves.&lt;/p&gt;

&lt;p&gt;Why it would be better to avoid actions from your side: it’s the same story as with the old API restore function. If the user doesn’t log in and you’re trying to refresh his transaction, he will unexpectedly see the screen that prompts him to authenticate. It might be shocking for users who didn’t take any action with the purchases. &lt;/p&gt;

&lt;h2&gt;
  
  
  How to handle switching business models with the new App Transaction API
&lt;/h2&gt;

&lt;p&gt;The new App Transaction API also allows to change your business model easily without affecting the access of existing customers.&lt;/p&gt;

&lt;p&gt;App Transaction API allows you to receive the necessary details about the user so you can change your business model from the paid app to free with in-app purchases or subscription monetisation. Previously, you would receive these details by processing the receipt. &lt;/p&gt;

&lt;p&gt;Let’s take a closer look. &lt;/p&gt;

&lt;p&gt;Previously, receipts combined all the information in one section.&lt;/p&gt;

&lt;p&gt;Like this: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mz07T554--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8083zs7t3pg8z2tkizn7.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mz07T554--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8083zs7t3pg8z2tkizn7.jpeg" alt="Image description" width="880" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, the information is divided into separate components in StoreKit: Transaction History and App Transaction.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lyaK16fL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hmfb5evnd6lwucv5ndtl.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lyaK16fL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hmfb5evnd6lwucv5ndtl.jpeg" alt="Image description" width="880" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first section includes the entire in-app purchase history: the user’s latest transactions, unfinished transactions, and current entitlements. You can receive all this information on your server as well through the App Store API.&lt;/p&gt;

&lt;p&gt;The second component, App Transaction, contains all the data you need to understand whether your user has ever purchased your app previously.&lt;/p&gt;

&lt;p&gt;So let’s take a look at the scenario of switching your business model from the paid app to the free app with subscriptions. &lt;/p&gt;

&lt;p&gt;Apple has provided a great example of such an app’s timeline. Let’s assume you have two users: the first one has bought the paid app (v2.5) and the second has purchased the subscription after you switched the monetisation model (v8.2). &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--avvmcuEx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yff1kht9zu2exto3ny37.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--avvmcuEx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yff1kht9zu2exto3ny37.jpeg" alt="Image description" width="880" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The change in your app’s business model means that there will also be changes in your access management logic for the app. &lt;/p&gt;

&lt;p&gt;For instance, you have several features that were previously available for all who purchased your paid app, and now these features are available as non-consumable purchases. Additionally, you’ve added some new cool features that are available with subscriptions. In this case, you should make sure that all your existing customers (v1.0 to &amp;lt;8.0) have access to the premium content they paid for. But you could still suggest that your user buy the new features that become available after switching the business model. Newcomers (v8.0 and higher v8.2) could purchase everything as it is. &lt;/p&gt;

&lt;p&gt;As we mentioned previously, in this case you should distinguish your users: distinguish the ones who bought your paid app from the newcomers.&lt;/p&gt;

&lt;p&gt;Let’s take a look at the code that will help us with it: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tRXzpwGd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9w581ocr328ccu4p4683.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tRXzpwGd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9w581ocr328ccu4p4683.jpeg" alt="Image description" width="880" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the first line of this code, we get the transaction. Then you should check whether it is verified or not. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If the transaction is not verified:&lt;/strong&gt; you can prompt your user to refresh the app transaction and restore the access, simply give the user access to the app but with minimal functionality, or suggest purchasing a subscription. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If the transaction is verified:&lt;/strong&gt; you should determine whether this user gets the app.&lt;/p&gt;

&lt;p&gt;App Transaction is contained in the field &lt;code&gt;originalAppVersion&lt;/code&gt; that displays the information about the app version that corresponds to this transaction. If this version is older than the version that you’ve released your new monetisation model, you need to grant the user the needed access. If the version is newer, you should check which access you should grant to your user. &lt;/p&gt;

&lt;p&gt;As you could see, just a few lines of code allow you to receive the verified transaction that contains all the needed details to understand which app version was purchased and when. As I mentioned, it could be possible just by processing of your receipt (that wasn’t too easy).&lt;/p&gt;

&lt;h2&gt;
  
  
  New Properties in StoreKit
&lt;/h2&gt;

&lt;h3&gt;
  
  
  PriceLocale
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developer.apple.com/documentation/storekit/product/3971084-pricelocale"&gt;PriceLocale&lt;/a&gt; is now included in product. But what has changed? &lt;/p&gt;

&lt;p&gt;Previously, to show your user the price, you could use &lt;a href="https://developer.apple.com/documentation/storekit/product/3749580-displayprice"&gt;displayPrice&lt;/a&gt;. This field contained currency and price. There was a &lt;a href="https://developer.apple.com/documentation/storekit/product/3749586-price"&gt;price&lt;/a&gt; field as well the price without the currency or locale. &lt;/p&gt;

&lt;p&gt;But what about the cases when you’d like to show your user how much he might save if he purchases the annual subscription instead of the monthly one? (And let’s be honest, this is a quite popular mechanic.) What if you also want to show how much he should pay for the annual subscription monthly?&lt;/p&gt;

&lt;p&gt;In this case, you can use &lt;code&gt;priceLocale&lt;/code&gt; to help you add information about the price and currency. The previous version of StoreKit had this functionality, but it was quite unstable – you just needed to fill the SKProduct field. But with the new API you can use just &lt;code&gt;priceLocale&lt;/code&gt; and show the price wherever you want. &lt;/p&gt;

&lt;h3&gt;
  
  
  Server Environment Property
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developer.apple.com/documentation/storekit/transaction/3963920-environment"&gt;Environment&lt;/a&gt; – server environment in which the transaction was processed. This is an extremely useful property that can be used for testing and analytics purposes. For instance, you could use it to distinguish production purchases from the sandbox purchases so you don’t consider the testing data in your analytics. Previously, this information was available in the receipt and reflected the entire receipt, which was super inconvenient. &lt;/p&gt;

&lt;h3&gt;
  
  
  Recent Subscription Start Date
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developer.apple.com/documentation/storekit/product/subscriptioninfo/renewalinfo/3976513-recentsubscriptionstartdate"&gt;RecentSubscriptionStartDate&lt;/a&gt; represents the most recent period of continuous subscription period (subscription with no more than a 60-day gap between any two subscribed periods).  In other words, it represents the earliest start date of a subscription in a series of auto-renewable subscription purchases that ignores all lapses of paid service shorter than 60 days. &lt;/p&gt;

&lt;p&gt;For instance, if a user purchased the monthly renewable subscription on 8th of June 2021, and then renewed it once in a year (8th of June 2022), and between these periods he didn’t use this app or subscription, this field will contain the first date: 8th of June. &lt;/p&gt;

&lt;p&gt;This is important information to determine the customer cohort that continuously uses your app. By knowing the information about this cohort, you can reward your loyal customers with bonuses and discounts. The information about unsubscribers will help you to take actions to &lt;a href="https://qonversion.io/blog/push-notifications-for-subscription-apps-to-win-lost-customers-back/"&gt;win back lapsed customers&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sentinel values
&lt;/h3&gt;

&lt;p&gt;All the new properties that we’ve described above will be available starting iOS15. It will be available if you build your app with Xcode14 (which is available in beta). And all the properties will be available in Production and Sandbox environments. &lt;/p&gt;

&lt;p&gt;What does sentinel values mean? &lt;/p&gt;

&lt;p&gt;If you are testing StoreKit with Xcode and refer to new fields/properties, you’ll receive placeholders values. These are fields with a zero values. &lt;/p&gt;

&lt;p&gt;This is a great screenshot that’ll help you to remember this information.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6K4eeMDZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uvhaguet4oxrw22ve5jq.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6K4eeMDZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uvhaguet4oxrw22ve5jq.jpeg" alt="Image description" width="880" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Btw, you can solve this problem super easily: just update your testing divide to iOS 16.&lt;/p&gt;

&lt;p&gt;One more great screenshot that’ll help you to identify these placeholders.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--r5QonvHB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ogzzhufzxc8s5y69zd9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r5QonvHB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9ogzzhufzxc8s5y69zd9.png" alt="Image description" width="880" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All these new properties are available with App Store Server API and App Store Sever Notifications as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  SwiftUI + StoreKit updates
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Offer Codes Redeem Modifier in SwiftUI
&lt;/h3&gt;

&lt;p&gt;Offer codes help you to acquire new customers and reward existing ones with discounts, free access, and more. With App Store Connect you can create unique custom codes. You can set a maximum redemption limit and choose whether or not to set an expiration date.&lt;/p&gt;

&lt;p&gt;The update is quite simple: offer code redemption sheet has its own view modifier in SwiftUI, so you can easily process redemption flow with just a few lines of code and a binding boolean value. Available with iOS 16. This is the same functionality that was available in StoreKit and UIKit, but now is available in SwiftUI as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8NnzjQvB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7rrg7qqpnnnszv6guho0.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8NnzjQvB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7rrg7qqpnnnszv6guho0.jpeg" alt="Image description" width="880" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Updated possibility to request an app review
&lt;/h3&gt;

&lt;p&gt;The second Swift UI Update is the possibility to request an app review. This also was available in UIKit, but from now on available for SwiftUI as well.&lt;/p&gt;

&lt;p&gt;The limitations and best practices are the same: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can show it max 3 times within a 365 days &lt;/li&gt;
&lt;li&gt;You can’t ask people to review the same app version several times&lt;/li&gt;
&lt;li&gt;You shouldn’t interrupt user flow &lt;/li&gt;
&lt;li&gt;You shouldn’t request a necessary review as a result of a user action (completing the game level or achieving the results within the app). The user should have an option to skip this step.
The code example:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--70_h-rMk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w4bh3z4zphw0kkhqiz9n.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--70_h-rMk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w4bh3z4zphw0kkhqiz9n.jpeg" alt="Image description" width="880" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  New API for StoreKit Messages
&lt;/h2&gt;

&lt;p&gt;Apple released quite an interesting mechanism to display the messages that are related to StoreKit. The simplest example: you increased the price for the purchase and you’d like to show the price increase consent alert to your users. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ocgRL_eJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nqzfcb4blkq99iit0fjl.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ocgRL_eJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nqzfcb4blkq99iit0fjl.jpeg" alt="Image description" width="768" height="1300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, let’s take a closer look at this flow.&lt;/p&gt;

&lt;p&gt;Your app goes to the foreground. StoreKit checks: are there some changes with the App Store? The App Store returns the message info to StoreKit, and StoreKit asks the listener for permission to display the message or defer it (if you’d like to create your own scenarios for these messages to appear). &lt;/p&gt;

&lt;p&gt;But if StoreKit doesn’t include any listener, it will display the message over your app. &lt;/p&gt;

&lt;p&gt;Our advice here is to add listener to not interrupt your user’s flow and to not affect user experience, so the user won’t receive automate screen messages during the meditation app experience. You can ensure customers have a great experience by making sure that messages don’t appear at unexpected times.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mOWfsbDl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mml9bjoeo88jthodt4h6.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mOWfsbDl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mml9bjoeo88jthodt4h6.jpeg" alt="Image description" width="880" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The realisation is not that difficult. As Apple highlights, there will be official documentation on how to implement this functionality soon.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application username
&lt;/h2&gt;

&lt;p&gt;This is a field in which you can add the unique ID of the users from your system just to connect your user with their purchases. Those familiar with StoreKit can tell that this field was available earlier: so what’s the update? &lt;/p&gt;

&lt;p&gt;Well.. you’re right, this field already exists and its title is &lt;a href="https://developer.apple.com/documentation/storekit/skmutablepayment/1506088-applicationusername"&gt;applicationUsername&lt;/a&gt;. It was receiving any string value, but the official documentation recommended sending UUID into this field. &lt;/p&gt;

&lt;p&gt;The update is that StoreKit2 includes the new additional field &lt;a href="https://developer.apple.com/documentation/storekit/transaction/3749684-appaccounttoken"&gt;appAccountToken&lt;/a&gt;. The main difference is that this field is geared to receive UUID format only, not any random string value. The great thing is that all UUID strings from the previous StoreKit will be available in the new version of StoreKit as a part of &lt;code&gt;appAccountToken&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With StoreKit2 this value will appear in &lt;code&gt;appAccountToken&lt;/code&gt;. It is a kind of backward compatibility (in case you use UUID in &lt;code&gt;applicationUsername&lt;/code&gt;). The needed value will appear in StoreKit Transaction, App Store API, and App Store Server Notifications. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ElRyedJ6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nwjnrklonui3vrpodq7h.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ElRyedJ6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nwjnrklonui3vrpodq7h.jpeg" alt="Image description" width="880" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we briefly discussed what was presented for in-app purchases during WWDC22, hope it was useful!&lt;/p&gt;

&lt;p&gt;We know a thing or two about in-app purchases as &lt;a href="https://qonversion.io/"&gt;Qonversion&lt;/a&gt; provides a complete cross-platform infrastructure that allows you to create and restore purchases, validate receipts, and provide your app with an accurate subscription status without the need to build your server. So, If you’d like to learn more about it or have any questions, feel free to ping me in comments here.&lt;/p&gt;

&lt;p&gt;If you'd like to learn more on &lt;a href="https://qonversion.io/blog/handling-storekit-errors/"&gt;how to handle StoreKit errors&lt;/a&gt;, please follow our guide. Also, recently I've created an article on how to solve &lt;a href="https://qonversion.io/blog/skerrordomain-code-0/"&gt;SKErrorDomain 0&lt;/a&gt;, feel free to check! &lt;/p&gt;

</description>
      <category>apple</category>
      <category>ios</category>
      <category>mobile</category>
      <category>wwdc</category>
    </item>
  </channel>
</rss>
