<?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: Yulia Kondrashova</title>
    <description>The latest articles on DEV Community by Yulia Kondrashova (@yuliiakondr).</description>
    <link>https://dev.to/yuliiakondr</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F851111%2Fb0e8d2da-6776-4b68-b3f0-e08fa3066eeb.jpg</url>
      <title>DEV Community: Yulia Kondrashova</title>
      <link>https://dev.to/yuliiakondr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yuliiakondr"/>
    <language>en</language>
    <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>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>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>Qonversion page is here!</title>
      <dc:creator>Yulia Kondrashova</dc:creator>
      <pubDate>Thu, 26 May 2022 09:33:22 +0000</pubDate>
      <link>https://dev.to/yuliiakondr/qonversion-page-is-here-4k8f</link>
      <guid>https://dev.to/yuliiakondr/qonversion-page-is-here-4k8f</guid>
      <description>&lt;p&gt;Hey everyone!&lt;br&gt;
We created Qonversion blogpage &lt;a href="https://dev.to/qonversion"&gt;here&lt;/a&gt;&lt;br&gt;
Sign up to see more quality blogposts with subscription app insights from our superteam&lt;br&gt;
Looking forward to see you there :)&lt;/p&gt;

</description>
      <category>mobile</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>The Ultimate Guide to Subscription Testing on Android</title>
      <dc:creator>Yulia Kondrashova</dc:creator>
      <pubDate>Fri, 29 Apr 2022 09:29:54 +0000</pubDate>
      <link>https://dev.to/yuliiakondr/the-ultimate-guide-to-subscription-testing-on-android-42bc</link>
      <guid>https://dev.to/yuliiakondr/the-ultimate-guide-to-subscription-testing-on-android-42bc</guid>
      <description>&lt;p&gt;You’ve already implemented &lt;a href="https://developer.android.com/google/play/billing/subscriptions#lifecycle"&gt;access management logic&lt;/a&gt; for your subscription app, so now it’s time for testing. Before submitting your app for Google Play Console review make sure it responds to changes in subscription status and grants users access to your premium content. Actions related to subscription lifecycle events should lead to user access updates. &lt;/p&gt;

&lt;p&gt;Today, we will follow detailed &lt;a href="https://developer.android.com/google/play/billing/test#subs"&gt;guidance&lt;/a&gt; from Google and explore the main subscription testing scenarios.&lt;/p&gt;

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

&lt;p&gt;Before we start, make sure that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There are active subscriptions in the Google Play Console&lt;/li&gt;
&lt;li&gt;The logic of purchasing subscriptions is implemented in your app&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sandbox environment setup
&lt;/h2&gt;

&lt;h3&gt;
  
  
  License testing
&lt;/h3&gt;

&lt;p&gt;First, we need to configure the &lt;a href="https://support.google.com/googleplay/android-developer/answer/6062777"&gt;License testers&lt;/a&gt; list to access all the possibilities of testing subscriptions. After adding a tester’s Gmail address in the Google Play Console you have to log in to the device with the email. License test users can purchase subscriptions for free and use one of two payment methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test card, always approves&lt;/li&gt;
&lt;li&gt;Test card, always declines&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  Test devices
&lt;/h3&gt;

&lt;p&gt;You can use both physical devices and emulators to test subscriptions. For emulators, make sure that it has the Play Store installed. You can check that by selecting Tools -&amp;gt; AVD Manager in Android Studio.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Test Subscription Periods
&lt;/h2&gt;

&lt;p&gt;Subscriptions automatically renew until they are canceled. Test subscriptions renew up to six times and then will be cancelled automatically. Subscription Duration in the Sandbox environment differs from production and is shortened for testing purposes: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TFsRttKJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eiiark8l827gozsdsarz.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TFsRttKJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eiiark8l827gozsdsarz.jpeg" alt="Image description" width="880" height="299"&gt;&lt;/a&gt;&lt;br&gt;
Duration of subscription states such as Grace period are also reduced in the Sandbox environment: &lt;/p&gt;

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

&lt;h2&gt;
  
  
  Test subscription-specific features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Purchase a subscription with successful payment
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Purchase a weekly subscription with the following payment method: Test card, always approves.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;Check that you have received an email from Google Play that confirms the subscription is purchased. &lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Make sure that your app unlocks the premium content after the purchase.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wait for five minutes for the next renewal. Restart the app and check whether you still have access to the content. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wait for 25 minutes for another five renewals. When the last renewal period ends, Google Play cancels the subscription. Check that you have received an email from Google confirming the cancellation of the purchase.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;Make sure that the premium content is no longer available for you.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Cancel an active subscription
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Make sure that you have an active subscription in your app.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open Google Play and click on the account icon at the top right of the screen. Choose Payments &amp;amp; Subscriptions -&amp;gt; Subscriptions.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;Tap on the active subscription. &lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;The Manage Subscription tab will be opened. Click on the button Cancel Subscription and confirm the cancellation. The subscription will be active for the entire current billing period. But the next renewal will not be processed. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make sure your app is still granting access during the current billing period. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After the end of the billing period, the subscription will be canceled. Make sure your app has ceased to provide access to the content. &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try to purchase a subscription with the fail method
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;In the purchase dialog, select the following payment method: Test card, always declines and click Subscribe.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Google Play will return an error.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make sure that your app handles the error correctly and doesn’t provide access to the premium content.  &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Access recovery
&lt;/h2&gt;

&lt;p&gt;Users sometimes need to restore active purchases when they reinstall your app for any reason or when they use the app on several devices. If the user uses the same Google account, your app should provide the possibility of restoring subscriptions to maintain access to the purchased content. For more information, see the &lt;a href="https://developer.android.com/google/play/billing/subscriptions#lifecycle"&gt;Handling the subscription lifecycle&lt;/a&gt; guide in the Google docs. The following test cases assume that you check active subscriptions at the start of your app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reinstall the app
&lt;/h3&gt;

&lt;p&gt;Make sure that you have an active subscription and the app provides entitlements to the premium content. &lt;br&gt;
Reinstall and open the app on the same device.&lt;br&gt;
Make sure that your app correctly grants access to the user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiple devices
&lt;/h3&gt;

&lt;p&gt;Run your app on device №1 and purchase a subscription.&lt;br&gt;
Make sure that you have access to the premium content. &lt;br&gt;
Install and open the app on device №2. &lt;br&gt;
Make sure that your app correctly grants access to the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free trials
&lt;/h2&gt;

&lt;p&gt;If you would like to give new subscribers the opportunity to use the subscription free of charge for a specified period of time, configure &lt;a href="https://developer.android.com/google/play/billing/subscriptions#free-trial"&gt;Free trial&lt;/a&gt; as Enabled and specify its duration in the subscription settings in the Google Play Console.&lt;/p&gt;

&lt;p&gt;By default, users can only get one free trial or intro price for all subscriptions in the app. You can change that behavior. Go to Google Play Console -&amp;gt; All Apps -&amp;gt; Your App -&amp;gt; Monetize -&amp;gt; Products -&amp;gt; Subscriptions -&amp;gt; Manage subscription settings.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Purchase a subscription with a trial
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Make sure that the subscription with a free trial hasn’t been purchased yet from the test Google account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Start purchasing a subscription with a free trial (a weekly subscription in the example). &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make sure that the purchase dialog notifies that you’ll be charged only after the trial ends:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Purchase a subscription with the approved test card.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check that you’ve been notified by Google via email that the subscription includes a free trial period.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make sure the app grants access to the premium content.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make sure that you have been charged for the full subscription amount only after three minutes (you will receive an email from Google).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The app should grant access to the premium content until the subscription expires and the subsequent automatic cancellation.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Cancel an active trial
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Repeat steps 1–4 from the previous case.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cancel the subscription via Google Play without waiting three minutes. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The subscription will remain active until the end of the trial. Make sure the app grants access to the premium content until the free trial period ends.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make sure that you haven’t been charged when the free trial period ends.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Introductory price
&lt;/h2&gt;

&lt;p&gt;If you would like to offer new subscribers a discounted price for a specified duration, you can configure an &lt;a href="https://developer.android.com/google/play/billing/subscriptions#intro"&gt;Introductory price&lt;/a&gt; in the subscription settings. In a test environment the Introductory price period lasts the same as the subscription test period. Google makes it possible to customize the intro price in different ways; choose the necessary one and test according to the corresponding scenario described below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Single payment
&lt;/h3&gt;

&lt;p&gt;New subscribers pay an introductory price only once for a specific period of time. After the end of the period, they pay the usual fixed subscription price. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Make sure that the subscription with an intro price hasn’t been purchased from the test Google account yet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Start the process of purchasing a subscription (example shows a weekly subscription).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check that Google offers to purchase a subscription at the introductory price.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Purchase a subscription with the approved test card.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wait for the end of the billing period. The subscription should renew at the regular price (you will receive an email from Google with the details). &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Recurring payment
&lt;/h3&gt;

&lt;p&gt;New subscribers pay an introductory price for a fixed number of billing periods. Then they pay the usual subscription price.&lt;/p&gt;

&lt;p&gt;Repeat steps 1–5 above.&lt;br&gt;
Wait for the end of the billing period. Make sure that the subscription renews at introductory price again (you will receive an email from Google with the details). &lt;br&gt;
Make sure that the third step was repeated for the number of billing periods specified in the intro price settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grace period
&lt;/h2&gt;

&lt;p&gt;Users may experience payment issues at the end of the billing period. While Google is trying to re-charge the money, you can still grant access to the subscription. Configure &lt;a href="https://developer.android.com/google/play/billing/subscriptions#grace"&gt;Grace period&lt;/a&gt; in the subscription settings in the Google Play Console for that purpose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grace period with payment resolution
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Purchase a monthly subscription with the approved test card.&lt;/li&gt;
&lt;li&gt;Open the Google Play app. Choose Account -&amp;gt; Subscriptions &amp;amp; Payments -&amp;gt; Subscriptions. Click your test subscription and change the payment method to “Test card, always declines.”&lt;/li&gt;
&lt;li&gt;After five minutes, payment should be declined. The subscription enters the grace period. &lt;/li&gt;
&lt;li&gt;Make sure that during the grace period the user still has access to the subscription.&lt;/li&gt;
&lt;li&gt;Change the payment method in Google Play to an approved test card. &lt;/li&gt;
&lt;li&gt;After another five minutes (duration of the grace period in the test environment), the subscription successfully renews, recovers, and exits grace period.&lt;/li&gt;
&lt;li&gt;The app should grant access to the premium content until the subscription expires and the subsequent automatic cancellation. &lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Grace period without payment resolution
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Please follow the Grace period and Account hold section below.
##Grace period and account hold
The subscription goes into the &lt;a href="https://developer.android.com/google/play/billing/subscriptions#account-hold"&gt;Account hold&lt;/a&gt; state when the user is experiencing payment issues and grace period isn’t configured or has ended without payment resolution. The user no longer has access to the subscription while Google is retrying the payment method. Unlike the Grace period, Account hold does not need to be configured, and its support is mandatory for all developers.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  With payment resolution
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Note: Before we start, make sure that the grace period is configured for the subscription.&lt;/em&gt; &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Purchase a monthly subscription with the approved test card.&lt;/li&gt;
&lt;li&gt;Change the payment method in Google Play to a declined test card. &lt;/li&gt;
&lt;li&gt;After five minutes, payment should be declined and the subscription should enter the Grace period. &lt;/li&gt;
&lt;li&gt;After another five minutes, payment should be declined again and the subscription should enter account hold. The app should stop giving access to the subscription content. &lt;/li&gt;
&lt;li&gt;Change the payment method in Google Play to an approved test card. &lt;/li&gt;
&lt;li&gt;In the next 10 minutes the subscription renews and exits Account hold.
&lt;/li&gt;
&lt;li&gt;Make sure the app grants access to the premium content until the subscription expiration and the subsequent automatic cancellation.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Without payment resolution
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Repeat steps 1–4 from the previous case above.&lt;/li&gt;
&lt;li&gt;After 10 minutes, the subscription should be canceled. Make sure that the user has lost access to the subscription.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Account hold and no grace period
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Account hold with payment resolution
&lt;/h3&gt;

&lt;p&gt;_Note: Before we start, make sure that the grace period isn’t available for the subscription. _&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Purchase a monthly subscription with the approved test card.&lt;/li&gt;
&lt;li&gt;Change the payment method in Google Play to a declined test card. &lt;/li&gt;
&lt;li&gt;After five minutes, payment should be declined and subscription should enter Account hold. Make sure that the user has lost access to the subscription.&lt;/li&gt;
&lt;li&gt;Change the payment method in Google Play to an approved test card. &lt;/li&gt;
&lt;li&gt;After 10 minutes (duration of the account hold state in the test environment), the subscription should recover, renew, and exit the Account hold.&lt;/li&gt;
&lt;li&gt;Make sure the app grants access to the premium content until the subscription expiration and the subsequent automatic cancellation. &lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Account hold without payment resolution
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Repeat steps 1–3 from the previous case above.&lt;/li&gt;
&lt;li&gt;After 10 minutes, the subscription will be canceled. Make sure that the user still doesn’t have access to the premium content.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Downgrade/Upgrade
&lt;/h2&gt;

&lt;p&gt;You can provide your active users with the ability to &lt;a href="https://developer.android.com/google/play/billing/subscriptions#change"&gt;upgrade&lt;/a&gt; and &lt;a href="https://developer.android.com/google/play/billing/subscriptions#change"&gt;downgrade&lt;/a&gt; subscriptions. In these cases the previous subscription is invalidated, and the new subscription takes place. Let’s have a look how the &lt;a href="https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode"&gt;ProrationMode&lt;/a&gt; value &lt;a href="https://developer.android.com/google/play/billing/subscriptions#proration"&gt;affects&lt;/a&gt; your subscribers while upgrading from a base to a premium subscription. The following test cases assume that the Upgrade button has already been added to your app.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Immediate upgrade
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Purchase a base subscription.&lt;/li&gt;
&lt;li&gt;Make sure your app grants access to the base content of your app.&lt;/li&gt;
&lt;li&gt;Click on the Upgrade button and purchase a premium subscription with the suitable value of the following ProrationMode values: &lt;code&gt;IMMEDIATE_WITH_TIME_PRORATION&lt;/code&gt;, &lt;code&gt;IMMEDIATE_AND_CHARGE_PRORATED_PRICE&lt;/code&gt;, &lt;code&gt;IMMEDIATE_WITHOUT_PRORATION&lt;/code&gt;, &lt;code&gt;IMMEDIATE_AND_CHARGE_FULL_PRICE&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The subscription is upgraded immediately. Make sure your app unlocks the premium content of your app.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Upgrade after renew
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Purchase a base subscription.&lt;/li&gt;
&lt;li&gt;Make sure your app grants access to the base content of your app.&lt;/li&gt;
&lt;li&gt;Click on the Upgrade button and purchase a premium subscription with the DEFERRED ProrationMode value.&lt;/li&gt;
&lt;li&gt;The subscription is upgraded only when it renews. Your app should unlock premium content only after renewing the subscription.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Refunds
&lt;/h3&gt;

&lt;p&gt;All of the previous scenarios were executed in a test environment. To test the refund, you need to buy a subscription for money in the production environment. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Please be familiar with Google’s &lt;a href="https://support.google.com/googleplay/answer/2479637?hl=en#zippy=%2Capps-games-in-app-purchases-including-subscriptions"&gt;Refund policy&lt;/a&gt; before making a purchase.&lt;/li&gt;
&lt;li&gt;Purchase a subscription.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://support.google.com/googleplay/workflow/9813244?p=refundAWF&amp;amp;visit_id=637749390999189216-1020565101&amp;amp;rd=1"&gt;Request a refund&lt;/a&gt; on Google Play&lt;/li&gt;
&lt;li&gt;Wait until Google processes your request (usually one day).&lt;/li&gt;
&lt;li&gt;After the completed refund, your app should stop granting access to the user.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;In this article we’ve explored the main testing scenarios, but there are a few more, such as &lt;a href="https://developer.android.com/google/play/billing/subscriptions#upgrade-downgrade"&gt;resignup&lt;/a&gt; or &lt;a href="https://developer.android.com/google/play/billing/test#promo"&gt;promo codes&lt;/a&gt; testing which are covered in the Google documentation.&lt;/p&gt;

&lt;p&gt;We hope you successfully tested your subscriptions and didn’t find any bugs. But even if you did, it’s great news to find them before publishing your app. Implementation of subscriptions has never been easy and requires tremendous resources for developing access management logic — there are so many scenarios and use cases. &lt;a href="https://qonversion.io/"&gt;Qonversion&lt;/a&gt; significantly simplifies this process by providing you with ready-to-go solutions so you can use purchase, restore and checkPermissions methods and manage your subscriptions without any issues. In any case, whether you use an in-house solution or Qonversion, please do your testing — it is a must. Remember: It is better to be safe than sorry; the trust of your users is everything. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Handling StoreKit Errors</title>
      <dc:creator>Yulia Kondrashova</dc:creator>
      <pubDate>Fri, 22 Apr 2022 09:34:54 +0000</pubDate>
      <link>https://dev.to/qonversion/handling-storekit-errors-2b6h</link>
      <guid>https://dev.to/qonversion/handling-storekit-errors-2b6h</guid>
      <description>&lt;p&gt;The error state is one of the five UI states. It is pretty important for users’ experience and satisfaction. If you don’t handle errors, it will be the main reason for users to lose control and as a consequence – a reduction in retention or even deletion of the app. &lt;/p&gt;

&lt;p&gt;Error handling is more important when you work with dependencies, especially when working with a library like StoreKit. In general, StoreKit is a black-box in which anything can happen. In this short article, we will explore how to handle failed transactions and give appropriate error messages to the user as a feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  NSError Overview
&lt;/h2&gt;

&lt;p&gt;I just want to remind you how NSError works. We will need it below. Any error that happens on iOS includes error domain, domain-specific error code, and additional information specific for the application. So, for us the most important fields are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;domain&lt;/li&gt;
&lt;li&gt;code&lt;/li&gt;
&lt;li&gt;userInfo&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Variety of errors
&lt;/h2&gt;

&lt;p&gt;Errors associated with payments, store products, and cloud services occur with the domain model &lt;code&gt;SKErrorDomain&lt;/code&gt; and error codes &lt;code&gt;SKError.Code&lt;/code&gt;. The entire list of errors includes more than 15 options, you can find them in this documentation. In addition to &lt;code&gt;SKErrorDomain&lt;/code&gt; errors, there may be network problems that occur in the &lt;code&gt;NSURLErrorDomain&lt;/code&gt; model. Let’s handle all of them. &lt;/p&gt;

&lt;h2&gt;
  
  
  Handling StoreKit errors
&lt;/h2&gt;

&lt;p&gt;For error handling, we need to implement two methods of the &lt;code&gt;SKRequestDelegate&lt;/code&gt; delegate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;optional func request(_ request: SKRequest, didFailWithError error: Error)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an optional method that receives errors from &lt;code&gt;SKRequest&lt;/code&gt;. It is important to keep in mind that when these errors occur, &lt;code&gt;(void) requestDidFinish: (SKRequest *) request&lt;/code&gt; will not be called.&lt;/p&gt;

&lt;p&gt;The second point of failure is transaction processing, here errors are quite varied and can be associated with both the fact that the user canceled the operation, and with the restriction of rights under parental control, for example. To receive errors when working with transactions, it is necessary to separately process the transaction status &lt;code&gt;SKPaymentTransactionStateFailed&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;- (void)paymentQueue:(nonnull SKPaymentQueue *)queue
 updatedTransactions:(nonnull NSArray&amp;lt;SKPaymentTransaction *&amp;gt; *)transactions {
  for (SKPaymentTransaction *transaction in transactions) {
      switch (transaction.transactionState) {
        case SKPaymentTransactionStatePurchasing:
          break;
        case SKPaymentTransactionStatePurchased:
          // implement handling purchased state
          // [self handlePurchasedTransaction:transaction];
          break;
        case SKPaymentTransactionStateFailed:
          [self handleFailedTransaction:transaction];
          break;
        case SKPaymentTransactionStateRestored:
          // implement handling restored state
          // [self handlePurchasedTransaction:transaction];
          break;
        default:
          break;
      }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Method &lt;code&gt;handleFailedTransaction&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;- (void)handleFailedTransaction:(SKPaymentTransaction *)transaction {
  NSError *error = transaction.error;

  if (error &amp;amp;&amp;amp; [[error domain] isEqualToString:SKErrorDomain]) {    
      switch (error.code) {
        case SKErrorUnknown:
          // Error code indicating that an unknown or unexpected error occurred.
          break;
        case SKErrorClientInvalid:
          // Error code indicating that the client is not allowed to perform the attempted action.
          break;
        case SKErrorPaymentCancelled:
          // Error code indicating that the user canceled a payment request.
          break;
        case SKErrorStoreProductNotAvailable:
          // Error code indicating that the requested product is not available in the store.
          break;
        case SKErrorPaymentNotAllowed:
          // Error code indicating that the user is not allowed to authorize payments.
          break;
        case SKErrorPaymentInvalid:
          // Error code indicating that one of the payment parameters was not recognized by the App Store.
          break;
        // Belowe codes available on different iOS
        case 6:
          // SKErrorCloudServicePermissionDenied
          // Error code indicating that the user has not allowed access to Cloud service information.
          break;
        case 7:
          // SKErrorCloudServiceNetworkConnectionFailed
          // Error code indicating that the device could not connect to the network.
          break;
        case 8:
          // SKErrorCloudServiceRevoked
          // Error code indicating that the user has revoked permission to use this cloud service.
          break;
        case 9:
          // SKErrorPrivacyAcknowledgementRequired
          // Error code indicating that the user has not yet acknowledged Apple’s privacy policy for Apple Music.
          break;
        case 10:
          // SKErrorUnauthorizedRequestData
          // Error code indicating that the app is attempting to use a property for which it does not have the required entitlement.
          break;
        case 11:
          // SKErrorInvalidOfferIdentifier
          // The specified subscription offer identifier is not valid
        case 12:
          // SKErrorInvalidSignature
          // The cryptographic signature provided is not valid
          break;
        case 13:
          // SKErrorMissingOfferParams
          // One or more parameters from SKPaymentDiscount is missing
          break;
        case 14:
          // SKErrorInvalidOfferPrice
          // The price of the selected offer is not valid (e.g. lower than the current base subscription price)
          break;
      }
  } else if (error &amp;amp;&amp;amp; [[error domain] isEqualToString:NSURLErrorDomain]) {
    switch (error.code) {
      case NSURLErrorNotConnectedToInternet:
        // A network resource was requested, but an internet connection has not been established and can’t be established automatically.
        break;
      default:
       // Handle other network errors here
      break;
    }
  }

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Types of errors
&lt;/h2&gt;

&lt;p&gt;Let’s take a look at all the possible cases of StoreKit errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  .unknownSKErrorUnknown code 0
&lt;/h3&gt;

&lt;p&gt;This error &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=0&lt;/a&gt; return usually happens when an unknown or unexpected error has occurred. Usually, there is no need to take any action on the developers side, but here are a few recommendations to check: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check the &lt;code&gt;LocalizedDescription&lt;/code&gt; property of the error object.&lt;/li&gt;
&lt;li&gt;If the error appeared during the testing, try logging out or try it with a new test user. &lt;/li&gt;
&lt;li&gt;The problem might be on the user’s account side, so it is worth asking the users to check and see if anyone gets back to you with more information.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  .SKErrorClientInvalid code 1
&lt;/h3&gt;

&lt;p&gt;This error is displayed when someone without the required permissions tries to perform the action. No action is needed to be done on your side. However, it is possible to try and show an automated notification with a message related to the action such as: “due to …. you are not allowed to perform the action. Please, change your account or device”.&lt;/p&gt;

&lt;h3&gt;
  
  
  .SKErrorPaymentCancelled code 2
&lt;/h3&gt;

&lt;p&gt;This error code indicates that the user canceled a payment request. No action is needed from your side, however, you could try to look at the user’s behavior to find the right trigger to encourage the user to complete the payment in the future – such as an offer or triggered email that talks about the value of your app. The best reaction to this error would be to catch the event and send the user an automated notification with a discount offer or something else of value to try and encourage them to continue using your app.&lt;/p&gt;

&lt;h3&gt;
  
  
  .SKErrorPaymentInvalid code 3
&lt;/h3&gt;

&lt;p&gt;This error shows that the payment was not processed correctly due to an issue with the billing – Error code indicating that one of the payment parameters wasn’t recognized by the App Store, card expiration or not enough funds. Try to check whether the billing issue is related to card expiration and if so, the best idea is to catch this error, and send an automated notification or email with a reminder to your user. &lt;/p&gt;

&lt;h3&gt;
  
  
  .SKErrorPaymentNotAllowed code 4
&lt;/h3&gt;

&lt;p&gt;An error code that indicates that the user is not allowed to authorize payments.&lt;/p&gt;

&lt;h3&gt;
  
  
  .SKErrorStoreProductNotAvailable code 5
&lt;/h3&gt;

&lt;p&gt;This error might happen when your user tries to purchase a product that is unavailable in their region. Check SKStorefront.&lt;/p&gt;

&lt;h3&gt;
  
  
  .SKErrorCloudServicePermissionDenied code 6
&lt;/h3&gt;

&lt;p&gt;Error code that indicates that the user has not allowed access to Cloud service information.&lt;/p&gt;

&lt;h3&gt;
  
  
  .SKErrorCloudServiceNetworkConnectionFailed code 7
&lt;/h3&gt;

&lt;p&gt;This error occurs when the user tries to complete an action without a proper internet connection. Your best bet is to show them a notification highlighting that their connection is unstable, such as “please make sure that you have a proper internet connection, and try again later”.&lt;/p&gt;

&lt;h3&gt;
  
  
  .SKErrorCloudServiceRevoked code 8
&lt;/h3&gt;

&lt;p&gt;Error code indicating that the user has revoked permission to use this cloud service.&lt;/p&gt;

&lt;h3&gt;
  
  
  .SKErrorPrivacyAcknowledgementRequired code 9
&lt;/h3&gt;

&lt;p&gt;Error code indicating that the user has not yet accepted Apple’s privacy policy for Apple Music.&lt;/p&gt;

&lt;h3&gt;
  
  
  .SKErrorUnauthorizedRequestData code 10
&lt;/h3&gt;

&lt;p&gt;Error code indicating that the app is attempting to access a property for which it does not have the required permissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  .SKErrorInvalidOfferIdentifier code 11
&lt;/h3&gt;

&lt;p&gt;This error indicates that the specified subscription offer identifier is not valid. For instance, you have not set up an offer with that identifier in the App Store, or you have revoked the offer. The best way to do this is to show the user a screen with your support team’s contact details.&lt;/p&gt;

&lt;h3&gt;
  
  
  .SKErrorInvalidSignature code 12
&lt;/h3&gt;

&lt;p&gt;The cryptographic signature provided is not valid. This error indicates that the promo offer purchase has been created incorrectly. There is no reason to show this error to the user – it’s best to handle this error and fix this within your infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  .SKErrorMissingOfferParams code 13
&lt;/h3&gt;

&lt;p&gt;One or more parameters from SKPaymentDiscount are missing. Same as above.&lt;/p&gt;

&lt;h3&gt;
  
  
  .SKErrorInvalidOfferPrice code 14
&lt;/h3&gt;

&lt;p&gt;This error indicates that the price of the selected offer is not valid, for instance, the price with a discount applied is lower than  the current base subscription price.&lt;/p&gt;

&lt;h3&gt;
  
  
  NSURLErrorNotConnectedToInternet
&lt;/h3&gt;

&lt;p&gt;A network resource was requested, but an internet connection has not been established and can’t be established automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to handle StoreKit errors?
&lt;/h2&gt;

&lt;p&gt;There are a lot of types of errors, so I‘d recommend dividing them into several groups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;external, which cannot be influenced in any way, for example, network problems&lt;/li&gt;
&lt;li&gt;request errors such as incorrect product ID&lt;/li&gt;
&lt;li&gt;user side errors such as canceling an operation or an unverified account&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  External errors
&lt;/h2&gt;

&lt;p&gt;For such errors, an alert about the error itself is enough, as well as a repeat button for starting purchasing again. For example, if there is no Internet or an error occurs when connecting to the App Store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Request errors
&lt;/h2&gt;

&lt;p&gt;This is a group of errors that can cause the greatest damage to the product conversions, as they are system problems, for example, &lt;code&gt;SKErrorStoreProductNotAvailable&lt;/code&gt; or &lt;code&gt;SKErrorInvalidOfferPrice&lt;/code&gt;. Errors of this group must be covered with additional logging, with the ability to remotely catch them on the server and fix them.&lt;/p&gt;

&lt;p&gt;User side errors &lt;br&gt;
This group of errors is directly related to the user’s state, for example, &lt;code&gt;SKErrorPrivacyAcknowledgmentRequired&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Correct error handling within the application is a very important part of the user experience. First of all, I would recommend implementing the error and (not too sure, would you say “implementing the error AT the loading state”, since both have to occur simultaneously)  the loading state and then start implementing the main (content) state. &lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>mobile</category>
      <category>ios</category>
      <category>storekit</category>
    </item>
    <item>
      <title>Setting Up Consumable and Non-consumable In-App Purchases</title>
      <dc:creator>Yulia Kondrashova</dc:creator>
      <pubDate>Fri, 22 Apr 2022 08:18:32 +0000</pubDate>
      <link>https://dev.to/qonversion/setting-up-consumable-and-non-consumable-in-app-purchases-1b2o</link>
      <guid>https://dev.to/qonversion/setting-up-consumable-and-non-consumable-in-app-purchases-1b2o</guid>
      <description>&lt;p&gt;When developing mobile applications, you may want to generate revenue with your app. According to the latest &lt;a href="https://go.sensortower.com/mobile-game-monetization-2022-ty.html?aliId=eyJpIjoicHZ0R2pmMzg1eVZ6elwvREciLCJ0IjoiT01xRnNINE53Z3EyU2loT3pKMGVCQT09In0%253D"&gt;Sensor Tower report&lt;/a&gt;, consumable and non-consumable in-app purchases are some of the most commonly used monetization mechanics — especially in games with Ad Removal. Games with this monetization method account for 90% of all games. In this article, we’ll take a closer look at features of in-app purchases, what’s the difference between consumable and non-consumable IAPs, and how to set it up with Qonversion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Types of In-app Purchases
&lt;/h2&gt;

&lt;p&gt;IAPs can be used in a number of ways. For example, to add features, remove ads, or purchase in-game currency. But which type of purchase should you use for each purpose?  &lt;/p&gt;

&lt;p&gt;There are two types of in-app purchases that can be used for iOS and Android mobile apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Non-consumable items. The idea is that they could be purchased only once. It is permanently associated with the user’s store account. Examples are purchasing game levels, race tracks, and extra app features, and yes – ad removal.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consumable items can be purchased multiple times. It could be used up during the life of the application. Examples are in-game currency and extras.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Differences Between Google and Apple In-app Purchases
&lt;/h2&gt;

&lt;p&gt;Google makes no difference between non-consumable and consumable in-app purchases when creating a product. However, Apple requires developers to specify the type of in-app purchase in App Store Connect:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_-zgnPj9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.statiqs.com/uploads/2022/03/29133520/app-store-connect.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_-zgnPj9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.statiqs.com/uploads/2022/03/29133520/app-store-connect.jpg" alt="alt text" width="880" height="733"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Setup Consumable and Non-consumable In-app Purchases
&lt;/h2&gt;

&lt;p&gt;Let’s assume we develop a racing game for iOS and Android. Our app allows users to unlock premium race tracks (non-consumable item, once a track is purchased, a user gets access to it forever) and purchase extra health for a car (consumable item, the user should be able to buy health every time he needs it). Qonversion allows handling all types of in-app purchases including non-subscription products.&lt;/p&gt;

&lt;p&gt;The following example describes the process of integrating in-app purchases into mobile apps with &lt;a href="https://qonversion.io/"&gt;Qonversion&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Before we get started with the creation of consumable and non-consumable purchases, make sure that you already have a &lt;a href="https://qonversion.io/"&gt;Qonversion account&lt;/a&gt; and have created your project and register the app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preparation
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Suppose we have already created in-app purchases in Google Play Console and App Store Connect with the following IDs: android_premium_track, android_extra_health and ios_premium_track, ios_extra_health accordingly. Let’s create Products in the Qonversion dashboard:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BYdYOaDU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.statiqs.com/uploads/2022/03/29133950/new-product-2048x839.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BYdYOaDU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.statiqs.com/uploads/2022/03/29133950/new-product-2048x839.jpg" alt="alt text" width="880" height="361"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3KQmV5Y2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.statiqs.com/uploads/2022/03/29134013/new-product-non-recurring-purchase-2048x862.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3KQmV5Y2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.statiqs.com/uploads/2022/03/29134013/new-product-non-recurring-purchase-2048x862.jpg" alt="alt text" width="880" height="370"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then we should create a Permission and link it to the premium_track product. Premium permission will unlock access to the track after its purchase.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lK46xav1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.statiqs.com/uploads/2022/03/29134042/new-permission-2048x852.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lK46xav1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.statiqs.com/uploads/2022/03/29134042/new-permission-2048x852.jpg" alt="alt text" width="880" height="366"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For the extra_health product, we don’t need to create a permission. Let’s also create an Offering to be able to change the products set remotely without releasing app updates. Attach created products to the offering:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0lZjWWqJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.statiqs.com/uploads/2022/03/29134108/new-offering-2048x1077.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0lZjWWqJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://blog.statiqs.com/uploads/2022/03/29134108/new-offering-2048x1077.jpg" alt="alt text" width="880" height="463"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How to Provide a Lifetime Access With Non-consumable In-App Purchases
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Displaying products
&lt;/h3&gt;

&lt;p&gt;Display products for sale to the user:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;iOS:&lt;/strong&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.offerings { (offerings, error) in
  if (error != nil) {
    // Handle the error here
  }
  let offering = offerings?.offering(forIdentifier: "standard_offering")
  if let product = offering?.product(forIdentifier: "premium_track") {
    // Display the product for sale
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Android:&lt;/strong&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.offerings(object : QonversionOfferingsCallback {
    override fun onSuccess(offerings: QOfferings) {
        val offering = offerings.offeringForID("standard_offering")
        offering?.productForID("premium_track")?.let { product -&amp;gt;
            // Display the product for sale
        }

    }

    override fun onError(error: QonversionError) {
        // Handle the error here
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Making Purchases
&lt;/h3&gt;

&lt;p&gt;When the user is ready to purchase the product, start a purchase flow with the following method:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;iOS:&lt;/strong&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.purchaseProduct(product) { (permissions, error, isCancelled) in
  if let premium: Qonversion.Permission = permissions["premium"], premium.isActive {
    // Successful purchase
    // Unlock premium content
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Android:&lt;/strong&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.purchase(context, product, callback = 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()) {
            // Successful purchase
            // Unlock premium content
        }
    }

    override fun onError(error: QonversionError) {
        // Handle the error here
    }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time you want to know whether the user has access to the premium content, you should call the checkPermissions() method. Once the product has been successfully purchased and the permission has become active, you should not allow the user to purchase the product again: The user has a lifetime access since now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Provide an Access With Consumable In-app Purchases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Displaying products
&lt;/h3&gt;

&lt;p&gt;Display products for sale to the user:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;iOS:&lt;/strong&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.offerings { (offerings, error) in
  if (error != nil) {
    // Handle the error here
  }
  let offering = offerings?.offering(forIdentifier: "standard_offering")
  if let product = offering?.product(forIdentifier: "extra_health") {
    // Display the product for sale
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Android:&lt;/strong&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.offerings(object: QonversionOfferingsCallback {
   override fun onSuccess(offerings: QOfferings) {
       val offering = offerings.offeringForID("standard_offering")
       offering?.productForID("extra_health")?.let { product-&amp;amp;gt;
           // Display the product for sale
       }

   }
   override fun onError(error: QonversionError) {
       // Handle the error here
   }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Making purchases
&lt;/h3&gt;

&lt;p&gt;When the user is ready to purchase the product, start a purchase flow with the following method:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;iOS:&lt;/strong&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.purchaseProduct(product) { (permissions, error, isCancelled) in
      if(!error){
         // Successful purchase
         // Unlock premium content
      }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Android:&lt;/strong&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.purchase(context, product, callback = object : QonversionPermissionsCallback {
    override fun onSuccess(permissions: Map&amp;lt;String, QPermission&amp;gt;) {
        // Successful purchase
        // Unlock content
    }

    override fun onError(error: QonversionError) {
        // Handle the error here
    }
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To unlock content, you don’t have to check permissions. Instead, you should rely on a successful result i.e., when there is no error. Note: The app has to remember the state of the purchased product: how many units were purchased and give access accordingly. For instance, if the user purchased 50 units of extra_health,and then used 20, you should consider it and demonstrate the actual balance of 30 pieces of health within your app. So, just 30 remaining pieces will be available for your user to purchase.&lt;/p&gt;

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

&lt;p&gt;In this blogpost, we took a closer look on how to set up consumable and non-consumable purchases. As you can see, the process is pretty simple. 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;/p&gt;

</description>
      <category>tutorial</category>
      <category>android</category>
      <category>mobile</category>
      <category>ios</category>
    </item>
  </channel>
</rss>
