<?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: Qonversion</title>
    <description>The latest articles on DEV Community by Qonversion (@qonversion).</description>
    <link>https://dev.to/qonversion</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%2Forganization%2Fprofile_image%2F5665%2F66151f8e-7d6b-498b-a125-3673d0cb1e96.jpg</url>
      <title>DEV Community: Qonversion</title>
      <link>https://dev.to/qonversion</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/qonversion"/>
    <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 Create Google Play Promo Codes for Android Apps</title>
      <dc:creator>Maria Bordunova</dc:creator>
      <pubDate>Wed, 19 Oct 2022 12:43:53 +0000</pubDate>
      <link>https://dev.to/qonversion/how-to-create-google-play-promo-codes-for-android-apps-55jg</link>
      <guid>https://dev.to/qonversion/how-to-create-google-play-promo-codes-for-android-apps-55jg</guid>
      <description>&lt;p&gt;Google Play Developer Console offers you a number of tools that help you grow your app business. &lt;a href="https://developer.android.com/google/play/billing/promo"&gt;Promotions&lt;/a&gt; are one of them. By using these, you can launch effective acquisition campaigns (and significantly increase your installs metric), partner programs or simply use these codes as incentives (as “thank you” or “sorry” gestures). But before launching such campaigns, you should be aware of how it works and consider the limitations of the &lt;a href="https://qonversion.io/blog/how-to-create-google-play-promo-codes/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=google-promo-codes"&gt;Google Play promo codes&lt;/a&gt;. So, let’s dive into the technical side. &lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;To start with promotional codes, you should &lt;a href="https://developer.android.com/google/play/billing/integrate"&gt;integrate the Google Play Billing Library&lt;/a&gt; into your app. Keep in mind that promotion redemptions can happen outside of your app or even when the app hasn’t been installed yet. It means that your app must handle purchases with promo codes correctly regardless of the app state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--15svakGv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c9hfxc3b6gnor0q5offd.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--15svakGv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c9hfxc3b6gnor0q5offd.jpeg" alt="Cookie Run Kingdom redeem flow: user should paste the Google Play promo codes on the coupon page and then claim the reward within the app" width="880" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of Google Play promo codes
&lt;/h2&gt;

&lt;p&gt;There are two types of Google Play promo codes: one-time and custom codes. The main difference is that one-time codes are automatically generated unique codes that could be used only once by a single person. The custom codes could be named universally (for instance, by name of the event where you promote your app) and used multiple times by multiple users according to your predefined limit. Custom codes are available only for subscriptions.&lt;/p&gt;

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

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

&lt;p&gt;Once you create a promotion, you can’t change the number of promo codes in that promotion or switch promo codes to a different type. But promo codes created for subscription promotions do not count towards your limit for non-subscription promotions, and vice versa. With that in mind, define your strategy wisely.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to create Google Play promo codes for your app
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Open the ‘Promo codes’ tab within Google Play Developer console.&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;Navigate to &lt;a href="https://play.google.com/apps/publish/(opens%20in%20a%20new%20tab)"&gt;Google Play Developer Console&lt;/a&gt; and select the &lt;strong&gt;‘All apps’&lt;/strong&gt; tab from the menu on the left. Then, select your app from the list. Select the &lt;strong&gt;‘Monetize‘&lt;/strong&gt; -&amp;gt; &lt;strong&gt;‘Promo codes‘&lt;/strong&gt; tab in the left menu bar.&lt;/p&gt;

&lt;p&gt;Click the &lt;strong&gt;‘Create promo code’&lt;/strong&gt; button.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;2. Specify the details of the promotion.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt; identifies the promo code in Google Play Console. Visible only to you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start date&lt;/strong&gt; must be in the future.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;End date&lt;/strong&gt; must be within one year of the start date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Details&lt;/strong&gt; on Google Play promo codes &lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;3. Choose promotion type.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Paid app:&lt;/strong&gt; Give users a &lt;a href="https://support.google.com/googleplay/android-developer/answer/6334373?hl=en#:~:text=Create%20a%20sale%20or%20promotion"&gt;paid app&lt;/a&gt; for free with promo code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-app product:&lt;/strong&gt; For non-subscription promotion link an existing in-app product from a drop-down list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subscription:&lt;/strong&gt; For subscription promotion specify an existing subscription and duration of the trial period. &lt;strong&gt;Note&lt;/strong&gt;: If a customer uses a promo code for a subscription that has already a free trial, the promo code will override the original free trial length.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;&lt;strong&gt;4. Create one-time promo code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the group box &lt;strong&gt;‘Code type’&lt;/strong&gt;, choose &lt;strong&gt;‘One-time codes’&lt;/strong&gt;. Enter the number of promo codes to be created. &lt;/p&gt;

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

&lt;p&gt;Click on the &lt;strong&gt;‘Save’&lt;/strong&gt; button at the right bottom of the page. It’ll open a popup window that notifies you to implement the billing library in your app. Click on &lt;strong&gt;‘Create’&lt;/strong&gt; in the window:&lt;/p&gt;

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

&lt;p&gt;After that, you’ll be able to download promo codes as a CSV file:&lt;/p&gt;

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

&lt;p&gt;That’s it! Now you can share these unique numbers via emails or personal event invitations, or simply share them with your support team so they can engage with your users more effectively.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.1 Create custom promo code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Enter a unique (per app) custom code using numbers (0-9), lowercase (a-z) and uppercase (A-Z). Also, specify the number of code redemptions:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rZrJhJR6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/30wfhi9dk51xxxb3q79l.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rZrJhJR6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/30wfhi9dk51xxxb3q79l.jpeg" alt="Image description" width="880" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the &lt;strong&gt;‘Save’&lt;/strong&gt; button at the right bottom of the page. It’ll open a popup window that notifies you to implement the billing library in your app. Click on &lt;strong&gt;‘Create’&lt;/strong&gt; in the window. This mechanic is widely used during events, summits, for influencer marketing. &lt;/p&gt;

&lt;h2&gt;
  
  
  User redemption flow
&lt;/h2&gt;

&lt;p&gt;Once we set up everything on the Google Developers Console side, let’s take a look at this process from the users’ standpoint. In the examples below, we’ll explore promotional codes for subscriptions.&lt;/p&gt;

&lt;h3&gt;
  
  
  In-app redemption flow
&lt;/h3&gt;

&lt;p&gt;The user can redeem a promo code as part of the normal purchase flow. Instead of purchasing the product, the user enters the code and gets the product, like a usual in-app purchase.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start the purchase flow for the product for which you created a promotion.&lt;/li&gt;
&lt;li&gt;Specify the Google payment method as &lt;strong&gt;‘Reedem code’&lt;/strong&gt;:
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Oug68tAS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rfu22eg6skiv1i3sc7kf.jpeg" alt="Image description" width="499" height="600"&gt;
&lt;/li&gt;
&lt;li&gt;Enter the promo code and select &lt;strong&gt;‘Redeem’&lt;/strong&gt;:
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1CnVnQWx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bcq6jm6wj3tsr8g184sy.jpeg" alt="Image description" width="600" height="583"&gt;
&lt;/li&gt;
&lt;li&gt;If the promo code was entered correctly, a window will appear with the subscription details and the trial duration. User needs to click on &lt;strong&gt;‘Continue’&lt;/strong&gt;.
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vm8MEoLT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mhwxo25fak8uqj6ccbmu.jpeg" alt="Image description" width="426" height="233"&gt;
&lt;/li&gt;
&lt;li&gt;The purchase window should open. User should click &lt;strong&gt;‘Subscribe’&lt;/strong&gt; there.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Google Play promo codes redemption flow
&lt;/h3&gt;

&lt;p&gt;A user can also redeem a promo code in Google Play:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the Google Play app.&lt;/li&gt;
&lt;li&gt;Open the menu by clicking the three lines in the left upper corner.&lt;/li&gt;
&lt;li&gt;Scroll down and select &lt;strong&gt;‘Redeem’&lt;/strong&gt;.
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CuAqptPG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ttypgl8ux8ncbo2cmqt7.jpeg" alt="Image description" width="337" height="600"&gt;
&lt;/li&gt;
&lt;li&gt;Enter the promo code and click &lt;strong&gt;‘Redeem’&lt;/strong&gt;:
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Vf1of_XC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r685xkyd3hxj6knc3o3w.jpeg" alt="Image description" width="600" height="583"&gt;
&lt;/li&gt;
&lt;li&gt;If the promo code was entered correctly, a window will appear with the subscription details and the trial duration. User needs to click &lt;strong&gt;‘Continue’&lt;/strong&gt;
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dI7d88L5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/amu03q5zblx44b0wl4bg.jpeg" alt="Image description" width="426" height="233"&gt;
&lt;/li&gt;
&lt;li&gt;The purchase window should open. User should click &lt;strong&gt;‘Subscribe’&lt;/strong&gt; there.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To save the user from having to enter a promo code, you can generate a URL with it. Share the URL that directs the user to Google Play Store.&lt;/p&gt;

&lt;p&gt;URL has the following format: &lt;a href="https://play.google.com/redeem?code=promo_code"&gt;https://play.google.com/redeem?code=promo_code&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Accelerate your app promotion strategy with Google Play promo codes
&lt;/h2&gt;

&lt;p&gt;Google Play promo codes are a universal tool for user acquisition and engagement that allows you to launch campaigns even with a limited budget. This is a great tool to increase the number of installs, but to convert these users to subscribers, provide them with great onboarding, and give them their “Aha!” moments as soon as possible.&lt;/p&gt;

&lt;p&gt;Google Play promo codes are also a great tool for user engagement and receiving reviews — just send your users a notification with the unique code at the right time and gift them access to the new experience. To find out &lt;a href="https://qonversion.io/blog/how-to-create-google-play-promo-codes/#:~:text=how%20to%20create%20custom%20push%20notifications%20on%20Android"&gt;how to create custom push notifications on Android&lt;/a&gt;, read this article.&lt;/p&gt;

&lt;p&gt;So there are so many things you should test. To see how your campaigns affect your subscription revenue, use Qonversion &lt;a href="https://qonversion.io/site/subscription-analytics"&gt;analytics tools&lt;/a&gt;. Try even more strategies with &lt;a href="https://qonversion.io/ab-testing"&gt;A/B testing&lt;/a&gt; and &lt;a href="https://qonversion.io/automation"&gt;Automation&lt;/a&gt;. And if you still have any questions — we’d be happy to hear from you! &lt;/p&gt;

</description>
      <category>android</category>
      <category>mobile</category>
    </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>Apple Fiscal Calendar 2023</title>
      <dc:creator>Yulia Kondrashova</dc:creator>
      <pubDate>Mon, 26 Sep 2022 20:11:52 +0000</pubDate>
      <link>https://dev.to/qonversion/apple-fiscal-calendar-2023-233m</link>
      <guid>https://dev.to/qonversion/apple-fiscal-calendar-2023-233m</guid>
      <description>&lt;p&gt;So, it’s that time of the year again when Apple starts their fiscal year. It is essential for an owner of a mobile app that works on Apple devices to know how to track the revenue and when you’ll be paid your App Store earnings. In this article we explained the main logic behind Apple’s financial calendar and here is a quick recap of how do the fiscal year works: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In general, Apple’s fiscal year is 364 days and it’s divided into four fiscal quarters (Q1, Q2, Q3, and Q4)&lt;/li&gt;
&lt;li&gt;Each fiscal year quarter starts with a 35-day fiscal month (5 fiscal weeks) and is then followed by two 28-day fiscal months (4 weeks each) &lt;/li&gt;
&lt;li&gt;The first day of the fiscal year is the last Sunday of September&lt;/li&gt;
&lt;li&gt;The first day of the week is Sunday (the same as the US calendar) &lt;/li&gt;
&lt;li&gt;Apple will release the 2023 Apple payment calendar in September of 2022 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ve prepared the preliminary Apple fiscal calendar 2023 for you below.&lt;/p&gt;

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

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

&lt;p&gt;Qonversion offers you an ability to track the revenue that you expect from Apple as well as other subscription app metrics such as churn, retention, refunds, renewals of in-app subscriptions, and other crucial aspects for your business KPIs. &lt;/p&gt;

&lt;p&gt;Click here to check &lt;a href="http://qonversion.io/blog/apple-fiscal-calendar-2023/?utm_source=dev.to&amp;amp;utm_medium=blog&amp;amp;utm_campaign=apple-fiscal-calendar"&gt;Apple fiscal calendar 2023&lt;/a&gt;&lt;/p&gt;

</description>
      <category>apple</category>
      <category>ios</category>
      <category>management</category>
      <category>mobile</category>
    </item>
    <item>
      <title>How to Configure Subscriptions on the Web with Stripe and Grant Users Access on iOS and Android</title>
      <dc:creator>Maria Bordunova</dc:creator>
      <pubDate>Thu, 11 Aug 2022 10:35:00 +0000</pubDate>
      <link>https://dev.to/qonversion/how-to-configure-subscriptions-on-the-web-with-stripe-and-grant-users-access-on-ios-and-android-1pfb</link>
      <guid>https://dev.to/qonversion/how-to-configure-subscriptions-on-the-web-with-stripe-and-grant-users-access-on-ios-and-android-1pfb</guid>
      <description>&lt;p&gt;Implementing renewable subscriptions is a daunting task, especially if you haven’t dealt with a third-party payment processors’ APIs like StoreKit, Google Billing Library, Stripe before. And it becomes much harder when you need to support several platforms simultaneously. You’ll find that managing cross-platform subscriptions is challenging as you need to solve technical integration complexity, consider different platforms’ policies and requirements, find the right approach to manage subscription statuses (cancellations, upgrades, downgrades, etc.), and analytics.&lt;/p&gt;

&lt;p&gt;At the same time, cross-platform subscription management provides your users with the best user experience on any device anywhere, and allows you to reduce platform fees (15-30% for app stores and 2-3% for Stripe — just feel the difference).&lt;/p&gt;

&lt;p&gt;In our &lt;a href="https://qonversion.io/blog/cross-platform-in-app-subscription-management-guide/" rel="noopener noreferrer"&gt;previous article&lt;/a&gt;, we covered granting access on the web after a purchase was made on iOS and Android platforms. Now let’s dig deeper into the web part and explore how to configure Stripe payments and grant your users premium access on iOS and Android apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting Stripe to Qonversion
&lt;/h2&gt;

&lt;p&gt;The first step is to connect Qonversion to your Stripe account. Navigate to your Qonversion project settings. Select Stripe and switch on the Stripe toggle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5q0jbza8rk08mtyo5vlg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5q0jbza8rk08mtyo5vlg.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
This will bring you to the Stripe sign in page. Click &lt;em&gt;Connect&lt;/em&gt; if you have an existing Stripe account or create a new one if required. If your Stripe account isn’t activated yet, you need to activate it (add business details, banking information etc).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F46k13gl4rr8sfl6q0gpb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F46k13gl4rr8sfl6q0gpb.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Setup Stripe Product and Permission
&lt;/h2&gt;

&lt;p&gt;Let’s assume you have already had the Qonversion &lt;a href="https://documentation.qonversion.io/docs/products" rel="noopener noreferrer"&gt;Product&lt;/a&gt; that contains Apple and Google product stores identifiers. If you want to sell a similar product with Stripe, update the existing product in Qonversion with the Stripe product ID.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6vnbswq9t6v8c48tdjql.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6vnbswq9t6v8c48tdjql.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
The Stripe product ID can be found in the Products tab in Stripe Dashboard. Just select the product and copy its ID shown at the top right corner of the page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3f0zjihdkjq7kyzue3jq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3f0zjihdkjq7kyzue3jq.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
Make sure that your Qonversion product is associated with &lt;a href="https://documentation.qonversion.io/docs/permissions" rel="noopener noreferrer"&gt;Permission&lt;/a&gt; that indicates premium status of a user.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1z9znt8fhlimtljascwj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1z9znt8fhlimtljascwj.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
If you want to sell subscriptions only on the web, you can just leave empty the Apple Store and Google Play Product ID fields in the Qonversion product details.&lt;/p&gt;
&lt;h2&gt;
  
  
  Sending Stripe Purchase Data to Qonversion
&lt;/h2&gt;

&lt;p&gt;You can directly call &lt;a href="https://documentation.qonversion.io/reference/overview" rel="noopener noreferrer"&gt;API methods&lt;/a&gt; or use our &lt;a href="https://github.com/qonversion/web-sdk/tree/main" rel="noopener noreferrer"&gt;Web SDK&lt;/a&gt; in your web app to send Stripe subscriptions data to Qonversion. Choose the method that is most suitable for you. If you have never worked with API endpoints before, our recommendation is to use Qonversion Web SDK to send Stripe purchases data.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. API methods
&lt;/h3&gt;

&lt;p&gt;Qonversion provides a high-performance REST API that allows you to create and identify users, send purchases data, get user entitlements, and more. This section of the article covers the methods you should use in order to get cross-platform access based on Stripe purchases.&lt;/p&gt;
&lt;h4&gt;
  
  
  1.1 Create User
&lt;/h4&gt;

&lt;p&gt;Before you start sending Stripe purchase data, you need to create a user. A user is a cross-platform entity with User object with the following endpoint:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;POST https://api.qonversion.io/v3/users/:user_id&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;User_id&lt;/code&gt; is the Qonversion User ID that will be registered for this user. Let’s use the following identifier &lt;code&gt;aa47b6fa&lt;/code&gt; as the &lt;code&gt;user_id&lt;/code&gt; in this example. &lt;/p&gt;
&lt;h4&gt;
  
  
  1.2 Identify the User
&lt;/h4&gt;

&lt;p&gt;User Identity allows cross-platform user identification and access management. Identity manages user access based on payments across different platforms. You can find more info about Identity in our &lt;a href="https://documentation.qonversion.io/docs/user-identifiers#3-user-identity" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Identify the user with the &lt;a href="https://documentation.qonversion.io/reference/create-an-identity" rel="noopener noreferrer"&gt;Identity&lt;/a&gt; method:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;POST https://api.qonversion.io/v3/identities/:identity_id&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;identity_id&lt;/code&gt; always use unique ID values. Otherwise, a user can get matched to another user’s premium status. You can use the user ID from your internal system as &lt;code&gt;identity_id&lt;/code&gt;. In our example, we will use &lt;code&gt;stripe_aa47b6fa&lt;/code&gt; as the &lt;code&gt;identity_id&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  1.3 Send Purchases
&lt;/h4&gt;

&lt;p&gt;Here we get to the most interesting part. Now everything is ready for sending user purchases data to Qonversion. Use the &lt;a href="https://documentation.qonversion.io/reference/create-a-purchase" rel="noopener noreferrer"&gt;purchases endpoint&lt;/a&gt; for this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;POST https://api.qonversion.io/v3/users/:user_id/purchases&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Below is the example with the parameters you should forward with the method body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
"currency": "USD",
"price": "100",
"purchased": 1659428809,
"stripe_store_data": {
   "subscription_id": "sub_1LSGVgL9K6ILzohYq5GCbktn",
   "product_id": "prod_MAbVQQaljmF6gm"
 }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to grant users with valid permissions, &lt;code&gt;product_id&lt;/code&gt;must be the same as Stripe Product Identifier that you provided in the previous step to the Qonversion Product &lt;em&gt;annual&lt;/em&gt;. &lt;code&gt;Subscription_id&lt;/code&gt; is an identifier for a &lt;a href="https://stripe.com/docs/api/subscriptions/object" rel="noopener noreferrer"&gt;Stripe Subscription object&lt;/a&gt;. Description of the rest parameters can be found in our &lt;a href="https://documentation.qonversion.io/docs/stripe-integration#4-send-stripe-purchases-to-qonversion" rel="noopener noreferrer"&gt;guide on sending Stripe purchases data to Qonversion&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If the purchase was created successfully, the response will be the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
   "currency": "USD",
   "price": "100",
   "purchased": 1659008000,
   "stripe_store_data": {
       "product_id": "prod_MAbVQQaljmF6gm",
       "subscription_id": "sub_1LSGVgL9K6ILzohYq5GCbktn"
   },
   "user_id": "aa47b6fa"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now check the Customers tab in your Qonversion account looking for your customer. Search is available with Qonversion &lt;code&gt;user_id(aa47b6fa)&lt;/code&gt; or &lt;code&gt;identity_id(stripe_aa47b6fa)&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fel9sm16r6y6bg11l3dhf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fel9sm16r6y6bg11l3dhf.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
And here are the details on customer level that you can quickly see in Qonversion dashboard including price and date of the purchased product:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr1uin5mos9d31brr5o1s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr1uin5mos9d31brr5o1s.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
Once you successfully send the purchase, Qonversion infrastructure handles all subscription changes like renewal, trial conversion, refund etc. This affects user permissions and is reflected in our &lt;a href="https://documentation.qonversion.io/docs/analytics" rel="noopener noreferrer"&gt;analytics dashboards&lt;/a&gt;. &lt;/p&gt;
&lt;h4&gt;
  
  
  1.4 Get Entitlements
&lt;/h4&gt;

&lt;p&gt;At this point, you should be able to receive the active permission that was configured in the previous step (&lt;em&gt;Setup Stripe Product and Permission&lt;/em&gt;). In our example, this is a permission with a premium ID. Call the following &lt;a href="https://documentation.qonversion.io/reference/retrieve-an-entitlement" rel="noopener noreferrer"&gt;method&lt;/a&gt; to check entitlements:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GET https://api.qonversion.io/v3/users/:user_id/entitlements&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Below you can see the example with entitlement response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
   "data": [
       {
           "active": true,
           "expires": 1690965033,
           "id": "premium",
           "product": {
               "product_id": "annual",
               "subscription": {
                   "current_period_type": "normal",
                   "renew_state": "will_renew"
               }
           },
           "started": 1659429033
       }
   ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Web SDK
&lt;/h3&gt;

&lt;p&gt;Web SDK is a TypeScript browser-client SDK that interacts with Qonversion API under the hood. With Qonversion’s Web SDK, you can easily identify users, send purchases, customize user properties, check an entitlement state, but it is still required to receive an entitlement state on the backend side. You can find SDK to be a more flexible and convenient way to share data to Qonversion from your web app.&lt;/p&gt;

&lt;h4&gt;
  
  
  2.1 Launch Qonversion Web SDK
&lt;/h4&gt;

&lt;p&gt;You can find detailed information on how to &lt;a href="https://documentation.qonversion.io/docs/web-sdk#launching-the-sdk" rel="noopener noreferrer"&gt;launch the SDK in the documentation here&lt;/a&gt;. You need to call the &lt;code&gt;Qonversion.initialize&lt;/code&gt; method and pass &lt;code&gt;QonversionConfigBuilder&lt;/code&gt; as a parameter:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const qonversionInstance = Qonversion.initialize(config);&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  2.2 Identify the user with Qonversion Web SDK
&lt;/h4&gt;

&lt;p&gt;User Identity allows cross-platform user identification and access management. Identity manages user access based on payments across different platforms. You can find more information about Identity in our &lt;a href="https://documentation.qonversion.io/docs/user-identifiers#3-user-identity" rel="noopener noreferrer"&gt;documentation here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Identify the user with the &lt;code&gt;identify&lt;/code&gt; method:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;await qonversionInstance.identify('identity_id');&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Always use unique ID values for &lt;code&gt;identity_id&lt;/code&gt;. Otherwise, a user can get matched to another user’s premium status. You can use your internal user ID from your system as &lt;code&gt;identity_id&lt;/code&gt;. In this example, we will use &lt;code&gt;stripe_aa47b6fa&lt;/code&gt; as the &lt;code&gt;identity_id&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  2.3 Send purchase
&lt;/h4&gt;

&lt;p&gt;Collect stripe purchase data and call the &lt;code&gt;sendStripePurchase method&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;сonst stripePurchaseData: PurchaseCoreData &amp;amp; StripeStoreData = {
  currency: 'USD',
  price: '100',
  productId: 'prod_MAbVQQaljmF6gm',
  purchased: 1659008486,
  subscriptionId: 'sub_1LSGVgL9K6ILzohYq5GCbktn'
};

const purchase = await qonversionInstance.sendStripePurchase(stripePurchaseData);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to grant users with valid permissions, &lt;code&gt;product_id&lt;/code&gt; must be the same as Stripe Product Identifier from the Qonversion Product &lt;code&gt;annual&lt;/code&gt; (see the previous step &lt;em&gt;Setup Stripe Product and Permission&lt;/em&gt;). &lt;code&gt;Subscription_id&lt;/code&gt; is an identifier for a &lt;a href="https://stripe.com/docs/api/subscriptions/object" rel="noopener noreferrer"&gt;Stripe Subscription object&lt;/a&gt;. Description of the rest parameters can be found in our &lt;a href="https://documentation.qonversion.io/docs/stripe-integration#4-send-stripe-purchases-to-qonversion" rel="noopener noreferrer"&gt;guide Send Stripe purchases to Qonversion&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If the purchase is created successfully, the response is the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
{
  currency: 'USD',
  price: '100',
  purchased: 1659008486,
  stripeStoreData: {
    subscriptionId: 'sub_1LSGVgL9K6ILzohYq5GCbktn',
    productId: 'prod_MAbVQQaljmF6gm'
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can check the Customers tab in your Qonversion account to see the details of this user. You can use &lt;code&gt;identity_id(stripe_aa47b6fa)&lt;/code&gt; to find the customer:  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe4hnqil2kwxk285w44z1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe4hnqil2kwxk285w44z1.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
You can see the details on user level including a purchased product price and the date of the purchase:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcr3q0430u1g51tmeu5m1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcr3q0430u1g51tmeu5m1.png" alt="Image description"&gt;&lt;/a&gt;&lt;br&gt;
Once you successfully send the purchase, Qonversion infrastructure handles all subscription states like renewal, trial conversion, refunds etc. It affects user permissions and shows up in &lt;a href="https://documentation.qonversion.io/docs/analytics" rel="noopener noreferrer"&gt;analytics&lt;/a&gt; as well. &lt;/p&gt;
&lt;h4&gt;
  
  
  2.4 Get entitlements
&lt;/h4&gt;

&lt;p&gt;At this point, you should get the active permission that was configured in the previous step (&lt;em&gt;Setup Stripe Product and Permission&lt;/em&gt;). In our example, this is a permission with a &lt;code&gt;premium ID&lt;/code&gt;. Call the &lt;code&gt;getEntitlements&lt;/code&gt; method:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const entitlements = await qonversionInstance.getEntitlements();&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Get Subscription Status and Unlock Premium Access on Mobile Apps
&lt;/h2&gt;

&lt;p&gt;The last step is to get a subscription status on an iOS or Android mobile app to handle user access. Launch the app and call the &lt;code&gt;identify&lt;/code&gt; method:&lt;/p&gt;

&lt;p&gt;iOS&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Qonversion.identify("identity_id")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Android&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Qonversion.identify("identity_id")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Identity ID&lt;/code&gt; must be the same you used when identifying the User on the web.&lt;/p&gt;

&lt;p&gt;Then call the &lt;code&gt;checkPermissions&lt;/code&gt; method:&lt;/p&gt;

&lt;p&gt;iOS&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Qonversion.checkPermissions { (permissions, error) in
  if let error = error {
    // handle error
    return
  }
  if let premium: Qonversion.Permission = permissions["premium"], premium.isActive {
    // handle the permission
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Android&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Qonversion.checkPermissions(object: QonversionPermissionsCallback {
    override fun onSuccess(permissions: Map&amp;lt;String, QPermission&amp;gt;) {
        val premiumPermission = permissions["premium"]
        if (premiumPermission != null &amp;amp;&amp;amp; premiumPermission.isActive()) {
        // handle the permission
        }
    }
    override fun onError(error: QonversionError) {
        // handle error here
    }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see here, a mobile apps user has the entitlements (access level) based on his stripe subscription.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fag2xs0f6fuh4jsqf33i4.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fag2xs0f6fuh4jsqf33i4.jpeg" alt="Image description"&gt;&lt;/a&gt; &lt;em&gt;Android / iOS&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;This article demonstrates how to manage users’ access on mobile apps based on their web subscriptions. The implementation is quick and straightforward.&lt;/p&gt;

&lt;p&gt;Once you implement cross-platform subscription management, you can easily track the performance of each platform in Qonversion analytics dashboards. Moreover, Qonversion offers a set of tools to leverage your subscription data including &lt;a href="https://qonversion.io/integrations" rel="noopener noreferrer"&gt;integrations&lt;/a&gt;, Apple Search Ads attribution, and push notifications.&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>ios</category>
      <category>android</category>
      <category>stripe</category>
    </item>
    <item>
      <title>5 ways to increase app valuation and score a beautiful EXIT webinar</title>
      <dc:creator>Yulia Kondrashova</dc:creator>
      <pubDate>Wed, 15 Jun 2022 13:52:56 +0000</pubDate>
      <link>https://dev.to/qonversion/5-ways-to-increase-app-valuation-and-score-a-beautiful-exit-webinar-36dp</link>
      <guid>https://dev.to/qonversion/5-ways-to-increase-app-valuation-and-score-a-beautiful-exit-webinar-36dp</guid>
      <description>&lt;p&gt;Hello team 😊&lt;/p&gt;

&lt;p&gt;On June 21, 2:00 p.m CEST, we will hold a webinar together with Alon Waller from Bluethrone, an app acquisition company. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Join this webinar to learn more about:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to measure and optimize the metrics that affect investors’ decisions&lt;/li&gt;
&lt;li&gt;Why ASA is a must-have tool for those who want to sell an app&lt;/li&gt;
&lt;li&gt;App Quoting Engine: how do investors determine what is the real value &amp;amp; growth potential of each app&lt;/li&gt;
&lt;li&gt;Investors’ insights: what will make investors want to pay more?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hurry up to save your spot! We’re waiting for you :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://qonversion.io/webinar-five-ways-to-increase-app-valuation"&gt;https://qonversion.io/webinar-five-ways-to-increase-app-valuation&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>tutorial</category>
      <category>discuss</category>
      <category>management</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>
    <item>
      <title>The new App Transaction API, Enhancements to StoreKit 2 and other WWDC22 updates for in-app purchases</title>
      <dc:creator>Yulia Kondrashova</dc:creator>
      <pubDate>Thu, 09 Jun 2022 11:44:12 +0000</pubDate>
      <link>https://dev.to/qonversion/the-new-app-transaction-api-enhancements-to-storekit-2-and-other-wwdc22-updates-for-in-app-purchases-70c</link>
      <guid>https://dev.to/qonversion/the-new-app-transaction-api-enhancements-to-storekit-2-and-other-wwdc22-updates-for-in-app-purchases-70c</guid>
      <description>&lt;p&gt;During WWDC 2022, Apple released the latest updates for in-app purchases. In this article, we’ll briefly walk you through the most significant improvements that have already been covered: enhancements to StoreKit 2 and App Store Server API. For more details you can refer to &lt;a href="https://qonversion.io/blog/whats-new-with-in-app-purchases-wwdc-2022/"&gt;our blog post&lt;/a&gt;, where you can find detailed description of these updates with advices for its implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. App transaction API is the new API to verify app purchases
&lt;/h2&gt;

&lt;p&gt;The app transaction represents the signed information about the purchase of your app for the device it’s running on. It is signed using JWS and it replaces the app detail portion of the app receipt from the original StoreKit API. With App transaction you can change your business model from the paid app to a free app with in-app purchases, check who out of your customers pre-order the app, and check purchase dates.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. New properties in the StoreKit models
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The first one is &lt;strong&gt;price locale&lt;/strong&gt;, which now is included in StoreKit products. It allows you to format numbers derived from decimal prices. For example, your customers may see how much a yearly subscription would cost them per month or how much they would save by purchasing your yearly service over your monthly service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Server Environment property&lt;/strong&gt; allows you to tell which server environment a transaction or renewal occurred in, which could be Xcode, sandbox or production. It filters out unnecessary information to prevent it from being sent up to your server&lt;/li&gt;
&lt;li&gt;Another one is &lt;strong&gt;Recent Subscription Start Date property&lt;/strong&gt;. You can use this to make informed decisions about your customers based on their subscription patterns. It represent the most recent period of continuous subscription (no more than 60-days gap between any two subscribed periods). It can help you to determine a pattern of loyalty between you and your customer.&lt;/li&gt;
&lt;li&gt;These properties return &lt;strong&gt;sentinel values&lt;/strong&gt; — placeholder values that signal that these are not the real values you should work with — in older operating system. To overcome this limitation you need to update your test device to a new operating system. You can take advantage of these new properties on devices as far back as last year’s operating system and it’s available in Xcode 14 and above&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h2&gt;
  
  
  3. SwiftUI API for redeeming subscription offer codes and asking your customers to review your app
&lt;/h2&gt;

&lt;p&gt;With App Store connect, you can create uniquely named custom codes that can help you to acquire retain and win back subscribers by providing subscriptions at a discount or free for a limited time. When a customer redeems an offer code for your app, the resulting transaction is sent to the transaction listener. Be sure to set up a transaction listener as soon as your app launches to receive new and updated transactions while your app is running. This offer code view modifier is available starting on iOS 16.&lt;/p&gt;

&lt;p&gt;Moreover, you can now ask for a review from your customer through the special sheet. You can decide the right time to request the review for your app, but you need to be aware that the prompt will only be displayed to customers a maximum of three times within a 365-day period and you shouldn’t ask customers to review the same version of your app multiple times.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. StoreKit Messages API used to display App Store messages to your customer
&lt;/h2&gt;

&lt;p&gt;StoreKit Messages represents a sheet that appears over your app to display an important information to the user. Messages are vended by the App Store and each message has a reason which is included in the Message metadata. StoreKit messages are retrieved when your app goes to foreground. For example, if you’ve increased the price of the subscription and it requires user consent, the App Store will inform affected subscribers through e-mail, push notifications, and an in-app price consent sheet. This will appear when a user opens your app, if they haven’t already responded to your price increase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does it work?&lt;/strong&gt;&lt;br&gt;
You app goes to the foreground. StoreKit checks whether there are 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 this messages to appear). But if StoreKit doesn’t include any listener, StoreKit will display the message over your app.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R5fNepGN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1e5vkm4y4p87fv0kalx3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R5fNepGN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1e5vkm4y4p87fv0kalx3.png" alt="Image description" width="880" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you’ve implemented in-app purchases, don’t forget to use the analytics tools to measure how much revenue each of your products brings. &lt;a href="https://qonversion.io/"&gt;Qonversion&lt;/a&gt; provides a complete infrastructure for in-app purchases and allows you to create and restore purchases, validate receipts, and provide your app with an accurate subscription status without a need to build your own server. If you’d like to learn more on how to set up and analyze in-app purchases and subscriptions, please read our &lt;a href="https://documentation.qonversion.io/docs"&gt;documentation&lt;/a&gt; or message us here.&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>ios</category>
      <category>wwdc</category>
    </item>
    <item>
      <title>The new subscription model and Google Play Billing Library 5.0 overview</title>
      <dc:creator>Kamo Spertsyan</dc:creator>
      <pubDate>Thu, 26 May 2022 07:43:25 +0000</pubDate>
      <link>https://dev.to/qonversion/the-new-subscription-model-and-google-play-billing-library-50-overview-2lo9</link>
      <guid>https://dev.to/qonversion/the-new-subscription-model-and-google-play-billing-library-50-overview-2lo9</guid>
      <description>&lt;p&gt;During the annual I/O conference, Google introduced its new major version of the Google Play Billing Library. Besides classic method deprecations and removals, there was also vast information about the new architecture of subscriptions, which aims to simplify the way you can create, manage and sell in-app purchases. In this article, I will explore the updates to Google Play Billing Library 5.0 and dig deeper into the most interesting ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new subscription model
&lt;/h2&gt;

&lt;p&gt;Google Play Billing System is the main tool that helps you to monetize your application with subscriptions. If you’re not familiar with this service yet, please read &lt;a href="https://qonversion.io/blog/a-complete-guide-to-google-play-in-app-purchases-and-subscriptions-implementation/" rel="noopener noreferrer"&gt;this article&lt;/a&gt;. With the latest version of its library, Google has changed the structure of how subscription products are defined, and this has led to changes in how they are sold in-app and managed on your backend. &lt;/p&gt;

&lt;p&gt;With Google Play Billing Library 5.0. Google has introduced new subscription architecture, which adds such entities as &lt;a href="https://support.google.com/googleplay/android-developer/answer/12154973" rel="noopener noreferrer"&gt;base plans and offers&lt;/a&gt;. Let’s take a look. &lt;/p&gt;

&lt;p&gt;Let’s say you have a premium subscription in your app and offer your clients weekly, monthly and annual subscriptions. Before these updates, you would have needed to create three different subscriptions - one per period. Then imagine that you wanted to offer a free trial to users who’ve left your app to persuade them to return. So, then you would also need to create one more annual subscription with a free trial.&lt;/p&gt;

&lt;p&gt;These are just two of the most common cases, causing the creation of lots of subscriptions with the previous subscription model - there could be many more examples. &lt;/p&gt;

&lt;p&gt;But from now on, you can handle all of these scenarios with just a single subscription. Google has divided a subscription model into three entities - subscription itself, base plans, and offers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffb4mot3yt1hjhgswx9au.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffb4mot3yt1hjhgswx9au.png" alt="The new subscription model"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The new subscription entity includes: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an identifier&lt;/li&gt;
&lt;li&gt;title&lt;/li&gt;
&lt;li&gt;description&lt;/li&gt;
&lt;li&gt;taxes information&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rest of the information is set up via base plans and offers. &lt;/p&gt;

&lt;p&gt;A base plan contains: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;its identifier &lt;/li&gt;
&lt;li&gt;renewal type (auto-renewing or prepaid) &lt;/li&gt;
&lt;li&gt;duration &lt;/li&gt;
&lt;li&gt;grace period &lt;/li&gt;
&lt;li&gt;prices and availability for different regions&lt;/li&gt;
&lt;li&gt;and some less important settings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the whole list of details in &lt;a href="https://support.google.com/googleplay/android-developer/answer/12154973" rel="noopener noreferrer"&gt;the official documentation&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;An offer contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;its identifier&lt;/li&gt;
&lt;li&gt;eligibility criteria - an option determining which users can access this offer (first-time subscribers, those who upgrade from another subscription, or developer determined), &lt;/li&gt;
&lt;li&gt;phases including free trials and single or recurring payments (phases usage is shown in the screenshot below) &lt;/li&gt;
&lt;li&gt;and a price discount - either fixed amount or percentage or absolute discount depending on the base plan price &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frindbqcgis2tsltgb9w3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frindbqcgis2tsltgb9w3.png" alt="Subscription phases"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each subscription can contain one or more base plans, which in turn may contain several offers (see the scheme above). So, for the example above you can create one premium subscription with three base plans - one per period, and an offer with a free trial for the annual base plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  Purchasing new subscriptions
&lt;/h2&gt;

&lt;p&gt;Speaking of code, in Google Play Billing Library 5.0, the &lt;code&gt;SkuDetails&lt;/code&gt; class is deprecated as well as every other entity or method containing &lt;code&gt;Sku&lt;/code&gt; in its name. Now you should consider using &lt;code&gt;ProductDetails&lt;/code&gt; for that purpose. Product details contain information about a subscription, including its base plans and offers.&lt;/p&gt;

&lt;p&gt;Before, you requested subscription details like those below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val params = SkuDetailsParams.newBuilder()
    .setType(BillingClient.SkuType.SUBS)
    .setSkusList(listOf("premium"))
    .build()

billingClient.querySkuDetailsAsync(params) { 
        billingResult, skuDetailsList -&amp;gt; // Process the result
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you should do the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val productList = listOf(
    QueryProductDetailsParams.Product.newBuilder()
        .setProductType(BillingClient.SkuType.SUBS)
        .setProductId("premium")
        .build()
)

val params = QueryProductDetailsParams.newBuilder()
    .setProductList(productList)
    .build()

billingClient.queryProductDetailsAsync(params) {
    billingResult, productDetailsList -&amp;gt; // Process the result
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, for launching the purchase flow, you used the following constructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val billingFlowParams = BillingFlowParams.newBuilder()
    .setSkuDetails(skuDetails)
    .build()

val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whereas now they should look like those that follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Note that `subscriptionOfferDetails` can be null if it is an in-app product, not a subscription.
val offerToken = productDetails.subscriptionOfferDetails?.get(selectedOfferIndex)?.offerToken ?: return

val productDetailsParamsList = listOf(
    BillingFlowParams.ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .setOfferToken(offerToken)
        .build()
)

val billingFlowParams = BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(productDetailsParamsList)

val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams) 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also look at the migration examples in &lt;a href="https://developer.android.com/google/play/billing/migrate-gpblv5" rel="noopener noreferrer"&gt;the Google official migration steps&lt;/a&gt;. Note, that there are a few typos in their examples which are fixed here.&lt;/p&gt;

&lt;p&gt;Let’s overview the changes. &lt;/p&gt;

&lt;p&gt;As I mentioned, you should now use &lt;code&gt;ProductDetails&lt;/code&gt; for querying subscription details and purchasing flow. In addition, you should provide an &lt;code&gt;offerToken&lt;/code&gt; to billing flow params as each &lt;code&gt;ProductDetails&lt;/code&gt; may contain several offers. Note, that the offer details array (&lt;code&gt;productDetails.subscriptionOfferDetails&lt;/code&gt;) always contains base plan details (if any base plan exists), so even if your subscription contains only a base plan without offers it will still have &lt;code&gt;offerToken&lt;/code&gt; for purchase.&lt;/p&gt;

&lt;p&gt;You could notice that the new purchase flow accepts multiple &lt;code&gt;ProductDetails&lt;/code&gt; instead of one &lt;code&gt;SkuDetails&lt;/code&gt; like in the previous version. But if you provide more than one &lt;code&gt;ProductDetails&lt;/code&gt;, the flow will end up with an error. &lt;a href="https://developer.android.com/google/play/billing/integrate#launch" rel="noopener noreferrer"&gt;The official integration guide&lt;/a&gt; gives the wrong example of using the &lt;code&gt;setProductDetails&lt;/code&gt; and &lt;code&gt;setOfferToken&lt;/code&gt; methods right on the &lt;code&gt;BillingFlowParams.Builder&lt;/code&gt; class, but there is only the &lt;code&gt;setProductDetailsParamsList&lt;/code&gt; &lt;a href="https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setProductDetailsParamsList(java.util.List%3Ccom.android.billingclient.api.BillingFlowParams.ProductDetailsParams%3E)" rel="noopener noreferrer"&gt;method available&lt;/a&gt; for those purposes. It seems as if Google decided to switch from single product details to multiple quite recently with a vision for the future when Billing will support multiple different subscription purchases at once.&lt;/p&gt;

&lt;p&gt;The rest flow - namely processing the purchase - remains the same as in the Google Play Billing Library 4.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  Subscriptions backward compatibility
&lt;/h2&gt;

&lt;p&gt;Well, everything sounds good so far, but what if you don’t want to migrate right now? Google has taken care of it.&lt;/p&gt;

&lt;p&gt;Despite Google Play Console already working only with the new subscription model, all the old subscriptions are converted to the new format automatically, saving their backward compatibility. This means that you see your subscriptions in the new format on Google Console but can still work with them as you did before on your app. &lt;/p&gt;

&lt;p&gt;You will also notice that all the old subscriptions are made read-only after migration. Google warns you that editing will disable InAppProducts API support for that subscription.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu95s1fvvp29ywow8y5hp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu95s1fvvp29ywow8y5hp.png" alt="Read-only subscription editing warning"&gt;&lt;/a&gt;&lt;br&gt;
You might be using this API in your backend for fetching some product details, so be careful - after making the subscription editable you will receive errors (as shown below) from that API if you try to fetch converted subscription info. The same will happen to all new subscriptions created after May, 11th.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP code - 422
Non-existent in-app product: com.qonversion.sample\/ProductId{productId=article_test_trial}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, if you use InAppProducts API, consider &lt;a href="https://developer.android.com/google/play/billing/compatibility#managing_subscription_status" rel="noopener noreferrer"&gt;upgrading it&lt;/a&gt; before creating new subscriptions or editing old ones.&lt;/p&gt;

&lt;p&gt;While surfing through the new subscriptions UI in Google Play Console, you will find “Backwards compatible” tags near base plans and offers of subscriptions. This means that if you purchase those subscriptions using deprecated &lt;code&gt;SkuDetails&lt;/code&gt; (as you do in Google Play Billing Library 4.0), you will buy exactly those compatible base plans/offers. If there is only a compatible base plan, without any compatible offer, then that base plan will be purchased. If there are both compatible base plan and offer available then if the offer is eligible to the current user, it will be purchased, else - base plan. You can choose another base plan/offer as backward compatible if you would like.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1apgby5mnzt30fu9tr2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1apgby5mnzt30fu9tr2w.png" alt="Choosing backward compatible base plan or offer"&gt;&lt;/a&gt;&lt;br&gt;
Note - this Backwards compatibility relates to Google Play Billing Library but not to InAppProducts API. As mentioned, if you begin using new features, for example, multiple base plans or offers for one subscription or just convert it from read-only to editable, that subscription can still be Backwards compatible for an app, but not for API.&lt;/p&gt;

&lt;h2&gt;
  
  
  The other updates
&lt;/h2&gt;

&lt;p&gt;There were several minor updates in the Google Play Billing Library 5.0 which are worth briefly mentioning.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The method &lt;code&gt;queryPurchases&lt;/code&gt; deprecated in Google Play Billing Library 4.0 was removed - no more synchronized requests, only &lt;code&gt;queryPurchasesAsync&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;launchPriceChangeFlow&lt;/code&gt; has been deprecated - &lt;a href="https://developer.android.com/google/play/billing/subscriptions#price-change" rel="noopener noreferrer"&gt;the new recommended price change flow&lt;/a&gt; requires clients' confirmation via the Google Play subscriptions page, so you should use deep links for navigation there instead of calling the &lt;code&gt;BillingClient&lt;/code&gt; method.&lt;/li&gt;
&lt;li&gt;Added the &lt;code&gt;setIsOfferPersonalized&lt;/code&gt; method to &lt;a href="https://developer.android.com/google/play/billing/integrate#personalized-price" rel="noopener noreferrer"&gt;indicate a personalized price&lt;/a&gt; for EU clients following the Consumer Rights Directive. This option adds a line to the bottom of the Google Billing purchase screen noting that a price is personalized.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The new subscriptions model and Qonversion
&lt;/h2&gt;

&lt;p&gt;While we are working on Google Play Billing Library 5.0 support, you can still use Qonversion SDK for your in-app purchases. We are now using Google Play Billing Library 4.0, which means we can manage only subscriptions with backward-compatible base plans and offers. You are still able to edit properties that were available in the previous subscriptions model like a price, grace period, trial duration, and so on as well as create new subscriptions. Just be sure that after you still have at least one backward compatible base plan and, if needed, a backward-compatible offer. And, we still require a whole subscription identifier for configuring products on our Product Center, not base plan or offer identifiers.&lt;/p&gt;

&lt;p&gt;If you have any questions, please let us know. We will be happy to assist you. &lt;/p&gt;

&lt;h2&gt;
  
  
  Useful links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/google/play/billing/release-notes#5-0" rel="noopener noreferrer"&gt;Google Play Billing Library 5.0 release notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/google/play/billing/migrate-gpblv5" rel="noopener noreferrer"&gt;Google Play Billing Library 4 to 5 Migration Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/google/play/billing/compatibility" rel="noopener noreferrer"&gt;May 2022 subscription changes guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.google.com/googleplay/android-developer/answer/12124625" rel="noopener noreferrer"&gt;Recent changes to subscriptions in Play Console&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.google.com/googleplay/android-developer/answer/12154973?hl=en" rel="noopener noreferrer"&gt;Understanding subscriptions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>android</category>
      <category>billing</category>
      <category>googleplay</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
