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

от автора

Всем привет, меня зовут Влад и я разработчик 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 для работы с другими платформами.

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


ссылка на оригинал статьи https://habr.com/ru/articles/568902/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *