Pull to refresh
0
Adapty
Сервис для аналитики и роста мобильных подписок

Android in-app purchases, часть 1: конфигурация и добавление в проект

Reading time7 min
Views13K

Всем привет, меня зовут Влад и я разработчик Android SDK для обработки платежей в мобильных приложениях в Adapty.

Внутренние покупки и в частности подписки являются наиболее популярным способом монетизировать приложение. С одной стороны, подписка дает разработчику возможность постоянно развивать контент и продукт, с другой стороны, благодаря им пользователь получает более высокое качество приложения в целом. Внутренние покупки облагаются 30% комиссией, но если пользователь подписан больше года или приложение зарабатывает меньше $1М в год, то комиссия составляет 15%.

Это первая статья из серии, посвящённой работе с внутренними покупками на Android. В этой серии мы охватываем темы от создания in-app purchases до серверной верификации платежей:

  1. Android in-app purchases часть 1: конфигурация и добавление в проект. - Вы тут

  2. Android in-app purchases часть 2: инициализация и обработка покупок.

  3. Android in-app purchases, часть 3: получение активных покупок и смена подписки.

  4. Android in-app purchases, часть 4: коды ошибок от Billing Library и как не облажаться с тестированием.

  5. Android in-app purchases, часть 5: серверная валидация покупок.

В этой статье мы разберём, как:

  • создавать продукты в Google Play Console;

  • конфигурировать подписки: указывать длительность, стоимость, пробные периоды;

  • получать список продуктов в приложении.

Создание подписки/покупки

Перед тем, как мы начнем, убедитесь, что

  1. У вас есть аккаунт разработчика в Google Play.

  2. Вы подписали все соглашения и готовы работать.

Перейдем к делу, а именно создадим наш первый продукт.

Переходим в наш аккаунт разработчика и выбираем нужное приложение.

Дальше в левом меню ищем секцию Продукты, выбираем Подписки и жмем на Создать Подписку.

Дальше попадаем в конфигуратор подписки, разберем важные вещи.

  1. Создаем ID, который потом используем в приложении. Хорошо, когда в ID мы кодируем период подписки и какую-то еще полезную информацию. Это позволяет создавать продукты в одном стиле, чтобы в будущем было проще анализировать статистику по покупкам.

  2. Название подписки, как пользователь ее увидит в магазине.

  3. Описание подписки, пользователь тоже это увидит.

Скроллим ниже и выбираем период подписки, в нашем случае это неделя, и конфигурируем стоимость.

Обычно вы задаете цену в базовой валюте аккаунта, а система автоматически переводит цены в разные валюты разных стран. Но вы также можете поправить цену в конкретной стране вручную.

Обратите внимание, что Google сразу указывает налог в каждой стране, это очень круто, а в App Store Connect такого нет.

Скроллим ниже и опционально выбираем:

  1. Бесплатный пробный период.

  2. Начальная цена. Промо-предложение на первые периоды оплаты.

  3. «Льготный период». То есть, если у пользователя проблема с оплатой, сколько дней вы продолжаете давать ему премиум доступ.

  4. Возможность подписаться заново из Play Market, а не из приложения, после отмены подписки.

Сравнение процесса покупки с App Store Connect

Несмотря на то, что подписки значительно лучше монетизируются на iOS, админка Play Console выглядит намного удобнее. Она работает быстрее, лучше и проще структурирована, качественно локализована.

Сам процесс создания продукта предельно упрощен. Здесь мы описали, как создавать продукты на iOS.

Получение списка продуктов в приложении

После создания продуктов перейдем к созданию архитектуры в приложении для приема и обработки покупок. В целом процесс следующий:

  1. Подключаем платежную библиотеку.

  2. Разрабатываем структуру класса для взаимодействия с продуктами из Google Play.

  3. Реализуем все методы обработки покупки.

  4. Подключаем серверную верификацию покупки.

  5. Собираем аналитику.

В этой части разберем первые два пункта.

Подключим Billing Library к проекту:

implementation "com.android.billingclient:billing:4.0.0"

На момент написания статьи актуальной версией является 4.0.0. Вы можете в любой момент заменить ее на другую версию.

Создадим класс-обертку, который будет инкапсулировать логику взаимодействия с Google Play, и проинициализируем в нем BillingClient из библиотеки Billing Library. Назовем такой класс BillingClientWrapper.

Наш класс будет реализовывать интерфейс PurchasesUpdatedListener. Мы сразу переопределим его метод onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList<Purchase>?), который вызывается после совершения покупки, но саму реализацию опишем уже в следующей статье.

import android.content.Context
import com.android.billingclient.api.*

class BillingClientWrapper(context: Context) : PurchasesUpdatedListener {

   private val billingClient = BillingClient
       .newBuilder(context)
       .enablePendingPurchases()
       .setListener(this)
       .build()

   override fun onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList<Purchase>?) {
       // here come callbacks about new purchases
   }
}

Google рекомендует, чтобы одновременно было не больше одного активного соединения BillingClient’а с Google Play, чтобы колбэк о совершенной покупке не вызывался несколько раз. Для этого целесообразно иметь единственный экземпляр BillingClient в классе-синглтоне. Класс в примере выше сам по себе синглтоном не является, но мы можем провайдить его с помощью dependency injection (например, используя Dagger или Koin) таким образом, чтобы в один момент времени существовало не больше одного экземпляра.

Для совершения любого запроса с помощью Billing Library нужно, чтобы у BillingClient’а в момент запроса было активное соединение с Google Play, но в какой-то момент оно может быть утеряно. Для удобства напишем обертку, чтобы любой запрос выполнялся только при активном соединении.

private fun onConnected(block: () -> Unit) {
   billingClient.startConnection(object : BillingClientStateListener {
       override fun onBillingSetupFinished(billingResult: BillingResult) {
           block()
       }

       override fun onBillingServiceDisconnected() {
           // Try to restart the connection on the next request to
           // Google Play by calling the startConnection() method.
       }
   })
}

Чтобы получить продукты, нам нужно знать их идентификаторы, которые мы задавали в маркете. Но для запроса этого недостаточно, нужно указать еще и тип продуктов (подписки или разовые покупки), поэтому общий список продуктов мы можем получить только путем «склеивания» результатов двух запросов.

Так как запрос на продукты асинхронный, нам нужен колбэк, в котором мы получим либо список продуктов, либо ошибку. Billing Library при ошибке возвращает один из определенных в ней BillingResponseCode, а также debugMessage. Создадим интерфейс колбэка и модель для ошибки:

interface OnQueryProductsListener {
  fun onSuccess(products: List < SkuDetails > )
  fun onFailure(error: Error)
}

class Error(val responseCode: Int, val debugMessage: String)

Напишем приватный метод для получения данных о продуктах конкретного типа, а также публичный метод, который «склеит» результаты от двух запросов и вернет итоговый список продуктов или ошибку.

fun queryProducts(listener: OnQueryProductsListener) {
   val skusList = listOf("premium_sub_month", "premium_sub_year", "some_inapp")

   queryProductsForType(
       skusList,
       BillingClient.SkuType.SUBS
   ) { billingResult, skuDetailsList ->
       if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
           val products = skuDetailsList ?: mutableListOf()
           queryProductsForType(
               skusList,
               BillingClient.SkuType.INAPP
           ) { billingResult, skuDetailsList ->
               if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                   products.addAll(skuDetailsList ?: listOf())
                   listener.onSuccess(products)
               } else {
                   listener.onFailure(
                       Error(billingResult.responseCode, billingResult.debugMessage)
                   )
               }
           }
       } else {
           listener.onFailure(
               Error(billingResult.responseCode, billingResult.debugMessage)
           )
       }
   }
}

private fun queryProductsForType(
   skusList: List<String>,
   @BillingClient.SkuType type: String,
   listener: SkuDetailsResponseListener
) {
   onConnected {
       billingClient.querySkuDetailsAsync(
           SkuDetailsParams.newBuilder().setSkusList(skusList).setType(type).build(),
           listener
       )
   }
}

Таким образом мы получили информацию о продуктах (SkuDetails), где есть локализованные названия, цены, тип продукта, а для подписок еще и период платежа, а также информация о начальной цене и пробном периоде (если доступно данному пользователю). Финальный класс выглядит так:

import android.content.Context
import com.android.billingclient.api.*

class BillingClientWrapper(context: Context) : PurchasesUpdatedListener {

   interface OnQueryProductsListener {
       fun onSuccess(products: List<SkuDetails>)
       fun onFailure(error: Error)
   }

   class Error(val responseCode: Int, val debugMessage: String)

   private val billingClient = BillingClient
       .newBuilder(context)
       .enablePendingPurchases()
       .setListener(this)
       .build()

   fun queryProducts(listener: OnQueryProductsListener) {
       val skusList = listOf("premium_sub_month", "premium_sub_year", "some_inapp")

       queryProductsForType(
           skusList,
           BillingClient.SkuType.SUBS
       ) { billingResult, skuDetailsList ->
           if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
               val products = skuDetailsList ?: mutableListOf()
               queryProductsForType(
                   skusList,
                   BillingClient.SkuType.INAPP
               ) { billingResult, skuDetailsList ->
                   if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                       products.addAll(skuDetailsList ?: listOf())
                       listener.onSuccess(products)
                   } else {
                       listener.onFailure(
                           Error(billingResult.responseCode, billingResult.debugMessage)
                       )
                   }
               }
           } else {
               listener.onFailure(
                   Error(billingResult.responseCode, billingResult.debugMessage)
               )
           }
       }
   }

   private fun queryProductsForType(
       skusList: List<String>,
       @BillingClient.SkuType type: String,
       listener: SkuDetailsResponseListener
   ) {
       onConnected {
           billingClient.querySkuDetailsAsync(
               SkuDetailsParams.newBuilder().setSkusList(skusList).setType(type).build(),
               listener
           )
       }
   }

   private fun onConnected(block: () -> Unit) {
       billingClient.startConnection(object : BillingClientStateListener {
           override fun onBillingSetupFinished(billingResult: BillingResult) {
               block()
           }

           override fun onBillingServiceDisconnected() {
               // Try to restart the connection on the next request to
               // Google Play by calling the startConnection() method.
           }
       })
   }

   override fun onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList<Purchase>?) {
       // here come callbacks about new purchases
   }
}

На этом все, в следующих статьях мы расскажем о реализации покупок, тестировании и обработке ошибок.

Про Adapty

Как видите, в процессе добавления покупок в приложения на Android много нюансов. Если использовать готовые библиотеки, всё будет проще. Советую познакомиться с Adapty — SDK для in-app покупок. Он не только упрощает работу по добавлению покупок:

  • Встроенная аналитика позволяет быстро понять основные метрики приложения.

  • Когортный анализ отвечает на вопрос, как быстро сходится экономика.

  • А/Б тесты увеличивают выручку приложения.

  • Интеграции с внешними системами позволяют отправлять транзакции в сервисы атрибуции и продуктовой аналитики.

  • Промо-кампании уменьшают отток аудитории.

  • Open source SDK позволяет интегрировать подписки в приложение за несколько часов.

  • Серверная валидация и API для работы с другими платформами.

Познакомьтесь подробнее с этими возможностями, чтобы быстрее внедрить подписки в своё приложение и улучшить конверсии.

Tags:
Hubs:
Total votes 2: ↑2 and ↓0+2
Comments5

Articles

Information

Website
adapty.io
Registered
Founded
Employees
11–30 employees
Location
США