In app purchase android что это

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

Это вторая статья из нашего цикла о реализации покупок на Android. В первой статье мы рассказывали о том, как создавать продукты в Google Play Console, сконфигурировать подписки и получить список продуктов в приложении.

В прошлой части мы создали класс-обертку для работы с Billing Library:

Класс-обертка для работы с Billing Library

Перейдем дальше к реализации покупки и дополним наш класс.

Создание экрана с подписками

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

На данном этапе для примера мы сделали упрощённый вариант пейволла:

пример пейволла в приложении Google Play

Итак, на нашем пейволле располагаются следующие элементы:

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

Кнопка восстановления прошлых покупок. Этот элемент необходим для всех приложений, в которых используются подписки либо non-consumable покупки.

Доработка кода для отображения информации о продуктах

В нашем примере четыре продукта:

две автовозобновляемые подписки («premium_sub_month» и «premium_sub_year»);

продукт, который нельзя купить повторно, — non-consumable (“unlock_feature”);

продукт, который можно покупать много раз, — consumable (“coin_pack_large”).

Для упрощения примера будем использовать Activity, в которую заинжектим BillingClientWrapper из предыдущей статьи, и layout с жестко заданным количеством кнопок для покупки.

Для удобства добавим словарь, где ключом является sku продукта, а значением — соответствующая кнопка на экране.

Объявим метод для отображения продуктов в UI, опираясь на логику из предыдущей статьи:

product.price — это уже отформатированная строка с указанием местной валюты для данного аккаунта, здесь никакое дополнительное форматирование не нужно; остальные поля в объекте класса SkuDetails также приходят уже с учетом локализации.

Запуск процесса покупки

Для проведения покупки необходимо вызвать метод launchBillingFlow() из главного потока приложения.

Добавим для этого метод purchase() в BillingClientWrapper :

У метода launchBillingFlow() нет колбэка, ответ вернется в метод onPurchasesUpdated() . Помните, мы в прошлой статье его объявили, но оставили на потом? Вот сейчас он нам понадобится.

Метод onPurchasesUpdated() вызывается при каком-либо результате после взаимодействия пользователя с диалогом покупки. Это может быть успешная покупка, отмена покупки (пользователь закрыл диалог, в этом случае приходит код BillingResponseCode.USER_CANCELED) или же какая-то другая ошибка.

По аналогии с интерфейсом OnQueryProductsListener из предыдущей статьи, объявим в классе BillingClientWrapper интерфейс OnPurchaseListener , с помощью которого будем получать либо покупку (объект класса Purchase — о кейсе, когда он может быть null даже в случае успеха, расскажем в следующей статье), либо ошибку, которую мы также объявляли в предыдущей статье:

И реализуем его в PaywallActivity:

Добавим логику в onPurchaseUpdated():

Если purchaseList не пустой, для начала для каждой покупки делаем проверку, что ее purchasedState равен PurchaseState.PURCHASED, потому что покупки также могут быть отложенными, и в этом случае флоу на данном этапе прекращается. Далее, согласно документации, нужно сделать серверную верификацию покупки, о ней мы расскажем в следующих статьях. После этого надо предоставить пользователю доступ к контенту и сообщить об этом в Google. Если не сообщить, то через три дня покупка автоматически отменится. Интересно, что это характерно только для Google Play, в то время как на iOS такого нет. Сообщить о предоставлении доступа к контенту можно двумя способами:

с помощью acknowledgePurchase() со стороны клиента;

либо Purchases.Products.Acknowledge/Purchases.Subscriptions.Acknowledge со стороны бэка.

В случае с consumable-продуктом вместо него нужно вызвать метод consumeAsync() , который под капотом делает acknowledge, а заодно дает возможность покупать этот продукт повторно. Это можно сделать только с помощью Billing Library: Google Play Developer API почему-то не предоставляет возможность делать это на бэкенде. Любопытно, что, в отличие от Google Play, в App Store и в AppGallery свойство consumable за продуктом жестко определено на уровне App Store Connect и AppGallery Connect соответственно. Справедливости ради, замечу, что консьюмить такие продукты из AppGallery нужно всё-таки явно.

Напишем методы для acknowledge и consume, а также две версии метода processPurchase() — в случае, когда у нас есть свой бэкенд и когда его нет.

Без серверной верификации:

С серверной верификацией:

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

Во втором примере acknowledge, конечно, тоже можно было сделать на клиенте, но так как здесь у нас есть бэкенд, всё, что можно сделать на бэке, лучше отдать бэку. Что касается ошибок на acknowledge и consume, их нельзя игнорировать, потому что если ни одно из этих действий не произойдет в течение трёх дней после того, как покупка получила статус PurchaseState.PURCHASED, она отменится, а пользователю вернут средства. Поэтому, если мы не можем это сделать на бэкенде, и даже после нескольких повторных попыток всё еще получаем ошибку, самый надежный способ — получать текущие покупки пользователя в каком-нибудь методе жизненного цикла, например в onStart() или onResume(), и пытаться повторить в надежде, что пользователь в течение трёх дней зайдет в наше приложение при работающем интернете :).

Читайте также:  Внешняя gps антенна для андроид

Таким образом, текущая версия класса BillingClientWrapper будет выглядеть так:

Вы могли задаться вопросом, почему кнопки активны для всех продуктов, независимо от того, покупал их пользователь или нет. Или что будет, если купить обе подписки: заменит ли вторая первую или они обе будут сосуществовать. Всё это в следующих статьях 🙂

Источник

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

​​Привет, я Влад, core разработчик Adapty SDK для встроенных покупок на Android. Это наша третья статья из цикла статей про внедрение покупок на Android. В этой серии мы полностью закрываем вопросы добавления покупок в приложениях в Google Play:

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

Cегодня мы рассмотрим еще две важные темы по реализации in-app purchases с Google Play Billing Library. Начнем с получения активных покупок пользователя, то есть действующих подписок и ранее купленных non-consumable продуктов. Купленные consumable-продукты сюда не входят, потому что вы их обменяли на голду законсьюмили, чтобы иметь возможность купить повторно.

Это нам может пригодиться, если мы, допустим, хотим сделать кнопку покупки такого продукта неактивной. Или, например, написать на ней, что это текущая подписка, если это подписка. Или просто дать пользователю доступ к оплаченным фичам.

Получение списка покупок

В Billing Library для получения активных покупок есть метод queryPurchasesAsync(String skuType, PurchasesResponseListener listener) (раньше использовался синхронный метод queryPurchases(String skuType), но с версии Billing Library 4.0.0 его задепрекейтили). Да, все подобные методы почему-то требуют указания типа продуктов, поэтому получение полного списка снова придется комбинировать из двух запросов.

Дополним наш BillingClientWrapper по аналогии с тем, как мы делали в предыдущих статьях:

В колбэке нам возвращается список объектов класса Purchase. Раньше, чтобы понять, к какому продукту относится покупка, нужно было вызвать ее метод getSku() и получить id продукта. Но в 2021 году на Google I/O были анонсированы покупки нескольких продуктов в рамках одной транзакции, поэтому в версии 4.0.0 getSku() сменился на getSkus() и возвращает список id продуктов, а еще добавился метод getQuantity(), который возвращает их количество. Правда, способ инициировать такую покупку я в документации не нашел: кажется, он пока не в релизе. Если что-то про это знаете, пишите в комментариях, ставьте лайки, жмите на колокольчик.

У метода queryActivePurchases() есть одна небольшая проблема — он берет покупки из локального кэша Play Services, и результат не всегда бывает актуален, особенно если покупка была совершена на другом устройстве. Лечится это несложным хаком, заодно добавим в BillingClientWrapper api для получения истории покупок:

Здесь колбэк возвращает список с самой последней покупкой каждого продукта, который пользователь когда-либо приобретал, а заодно синхронизирует локальный кэш активных покупок с актуальным состоянием. Также обратите внимание, что покупка здесь представлена не классом Purchase, а классом PurchaseHistoryRecord.

Теперь мы можем объявить метод получения только что синхронизированных активных покупок:

Мы не просто так начали с получения покупок – следующая тема тесно связана с ними. Поговорим о замене подписок.

Смена подписок

В базовом случае, если купить обе подписки тем способом, который мы описали в предыдущей статье, они обе будут активны. Это не всегда ожидаемое поведение, поэтому, например, в App Store, как и в случае с consumable/non-consumable, это решается на уровне App Store Connect с помощью групп подписок. А с продуктами из Play Market, как и в случае с consumable/non-consumable, эту логику нужно делать на клиенте.

В нашем примере из предыдущей статьи подписки отличаются только периодом оплаты, поэтому, по логике, должны быть взаимоисключающими. Добавим в BillingClientWrapper метод для замены старой подписки на новую:

От обычной покупки он отличается только параметром SubscriptionUpdateParams. В классе SubscriptionUpdateParams.Builder нас интересуют 2 метода:

setOldSkuPurchaseToken(String purchaseToken) — метод, в который мы передаем purchaseToken той активной покупки, где purchase.skus.first() равен id подписки, которую мы хотим заменить (мы получали список активных покупок в том числе и для этого).

Каждая из констант говорит о том, когда подписка должна будет фактически смениться, а также какие финансовые транзакции должны будут произойти и в какой день. Рассмотрим на примерах.

В нашем случае есть 2 подписки: помесячная за $9.99 и годовая за $49.99. Для удобства расчетов округлим их до $10 и $50 соответственно. Представим, что 1 сентября пользователь купил помесячную подписку, а 15-го решил сменить ее на годовую. Какие есть варианты:

Подписка меняется немедленно. Так как прошло всего полмесяца, остались неиспользованные $5 от помесячной подписки, то есть 1/10 стоимости годовой – их хватает на 36 дней. Таким образом, $50 за годовую подписку пользователь заплатит 22 октября, следующее продление будет 22.10.2022 и т.д.

Этот режим не подходит для нашего соотношения цен, потому что для него стоимость новой подписки за единицу времени должна превышать стоимость старой подписки за ту же единицу времени (у нас получается $50/г.

Читайте также:  Модуляция голоса для андроид

$4.17/мес. onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList

?) был клиффхэнгер, что даже при успехе purchaseList может быть null – это тот самый кейс.

Помесячная подписка сменится на годовую только 1 октября, тогда же с пользователя снимут $50. Но колбэк onPurchaseUpdated(billingResult: BillingResult, purchaseList: MutableList

?) вызовется уже сейчас, только в параметре purchaseList придет null.

В этом кейсе Google настоятельно рекомендует делать acknowledge новой подписки на бэке при получении нотификации SUBSCRIPTION_RENEWED , которая придет только 1 октября, потому что, как мы помним, если этого не сделать, подписка отменится через 3 дня, и если пользователь в начале октября не будет заходить в приложение, выполнить acknowledge на клиенте нет шансов.

Впрочем, всё, что связано с бэком, расскажем уже в пятой статье.

Про Adapty

А пока предлагаю вам подписаться на наш блог на Habr, чтобы не пропустить следующие материалы. Мы пишем про покупки в мобильных приложениях: от технической реализации до метрик подписок и монетизации приложений; а также записываем подкаст, где общаемся с интересными гостями на тему приложений.

И ещё советую познакомиться с Adapty SDK, с которым работа с подписками становится проще не только технически:

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

А/Б тесты пейволлов делаются налету и помогают увеличивать конверсию в платных пользователей.

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

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

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

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

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

Источник

In-app purchases: продвинутые механики работы с покупками на Android и iOS

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

iOS in-app purchases:

Android in-app purchases:

Написано совместно с:

Андреем Кяшкиным,

В заключительной статье мы решили не замыкаться на одной платформе, а рассказать о продвинутых практиках работы с покупками на iOS и Android и показать, как сервер расширяет наши возможности при работе с подписками и какие преимущества даёт.

Работа с продуктами на сервере

Для получения данных о продуктах на iOS/Android с помощью StoreKit/Billing Library нам нужно передавать id продуктов, информацию о которых мы хотим запросить — то есть, нет способа получить все доступные продукты, не зная о них вообще ничего. Бэкенд дает нам замечательное преимущество — не зашивать id продуктов на клиенте и тем самым иметь возможность манипулировать списком актуальных продуктов без обновления приложения.

Таким образом, мы можем отдавать на клиент общую модель продукта:

product_id — это id продукта в сторе. Может возникнуть вопрос, почему таких полей, как описание продукта или цена, в этой модели нет. Тут важно понимать, что создание продуктов в соответствующих консолях никто не отменял, просто часть клиентской логики мы переносим на бэк, а другую ее часть мы перенести не можем. Помимо самого совершения покупки, которое без участия сторов невозможно (пока ещё), информацию о продукте по id тоже нужно получать от стора напрямую, потому что Apple и Google отдают локализованные значения в зависимости от того, к какой стране привязан данный аккаунт.

introductory_offer_eligibility , promotional_offer_eligibility и promotional_offer_id — это поля, которые несут в себе информацию о скидочных предложениях, доступных конкретному пользователю. В отличие от предыдущего пункта, эти штуки мы как раз вынесли на бэк, потому что — внезапно — ни AppStore, ни Play Market напрямую не дают информацию, доступно ли intro или promo для конкретного пользователя. Соответственно, у самого продукта может быть intro, но данному пользователю оно недоступно, потому что он его уже использовал. И чтобы отобразить это в UI, вычислять это нужно самостоятельно, лучше на бэке.

И теперь флоу получения продуктов на клиенте будет выглядеть так:

Получаем историю транзакций от стора на девайсе.

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

Запрашиваем у бэка продукты.

По id продуктов запрашиваем у стора всю остальную информацию.

Отображаем в UI.

Предоставление кросс-доступа пользователю

Как только вы вынесли продукты на сервер и сделали само понятие «продукт» универсальным, стоит задуматься о том, как сделать то же самое для понятия «статус покупки». Идея в том, что если у вас больше одной платформы, то покупка на одном устройстве должна разблокировать доступ к контенту на любом устройстве вне зависимости от ОС, но в пределах аккаунта, с которого она была совершена. Если у вас больше одного продукта, то сделать это не так просто, т.к. вам на устройстве необходимо понимать, что именно купил пользователь, и давать ему доступ к той или иной части вашего приложения.

Для универсализации статуса покупки удобно ввести понятие «уровень доступа», который выставляется пользователю при совершении покупок из определенного набора. Например, мы добавляем четыре продукта — по два на каждую платформу, недельная премиум и недельная премиум плюс. Пример из жизни — Silver и Gold подписка в Тиндере. Gold стоит дороже и открывает больше возможностей. За обычными недельными мы закрепляем уровень доступа «обычная», а за премиум подписками, соответственно, «премиум». Если пользователь на какой-либо платформе или девайсе покупает подписку, за которой, например, закреплен уровень доступа «обычная» то сервер должен после покупки прислать на устройство этот идентификатор (внутри профиля или каким-то другим способом) и, выполнив обычное сравнение строк, вы сможете понять, что именно купил пользователь и отреагировать на это.

Читайте также:  Android для surface pro

Так выглядит проверка на наличие уровня доступа:

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

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

Синхронизация истории покупок и восстановление покупок

Помимо всего прочего, также важно уметь правильно работать с историей покупок пользователя. Например, это актуально в случае переустановки приложения, если у вас предусмотрена только анонимная или неявная авторизация, т.е. нельзя сразу понять, есть ли у пользователя платный доступ. В этом случае достаточно будет получить историю покупок и отправить ее на сервер для обработки. Сделать это можно следующими способами:

просто запросив рецепт (receipt) — актуально, если в пределах установки уже были совершены какие-то покупки;

запустив процесс восстановления покупок (restore purchases).

А вот на iOS 15+ появилась возможность получать все entitlements пользователя в любой момент.

О получении рецептов мы уже писали в предыдущих статьях, поговорим чуть больше про восстановление. Восстановление не стоит запускать сразу же на старте, т.к. оно вызывает показ окна для пользователя, где требуется ввести логин и пароль от аккаунта. Подобный интерфейс на старте без каких-либо предшествующих действий со стороны пользователя может отпугнуть и создать ощущение мошенничества.

Как только пользователь сам инициировал процесс восстановления покупок, то тут уже можно спокойно обращаться к системному фреймворку работы с покупками. После успешного восстановления необходимо пересинхронизировать все покупки пользователя на сервер, что позволит определить текущее состояние его подписки; понять, доступны ли ему скидки; возможно составить какую-то аналитику для этого аккаунта.

В iOS 15+ появился listener, который в две строки можно запустить на старте приложения. Этот listener получает все текущие покупки, которые есть у пользователя, в виде массива и на старте обновляет их данные – статус и проч. В целом, при первом запуске приложения рестор покупок больше не нужен, т.к. в реал-тайме можно получить обновления по всем покупкам пользователя между разными девайсами.

Обработка покупок, когда сервер не отвечает

К сожалению, такое бывает. Если сервер лежит и покупка в это время невозможна, вы теряете не только подписчиков, но и деньги, как в настоящем, так и будущие автосписания, которые были бы возможны, если бы покупка состоялась.

При этом с ошибками от сторов на iOS или Android, как правило, сложно что-то сделать, кроме как показать ошибку в UI, а вот в случае недоступности нашего бэка мы можем чуть лучше сгладить углы.

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

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

Второе критичное место, где нам очень нужен бэк – это валидация покупки. Чтобы не расстраивать пользователя, можно сразу дать ему доступ, но при этом пытаться провалидировать покупку при любом удобном случае, например, при следующем запуске приложения или при возвращении приложения в foreground. Решение дать пользователю доступ к контенту без всех проверок может показаться поспешным, но если у вас преимущественно онлайн-контент, нечестным пользователям будет сложно пользоваться приложением без интернета, и рано или поздно неправомерный доступ будет отозван.

Заключение

В этой статье мы затронули более сложные механики работы с продуктами и покупками. Как правило, о них задумываются сразу после того, как сделали базовую интеграцию и основные механики уже работают: показ экрана оплаты, отображение актуальной информации о продуктах, покупка, обработка самых популярных ошибок.

Как видите, процесс подключения покупок довольно трудоёмкий и требует учёта очень разных сценариев. Упростить задачу можно с помощью сторонних сервисов. Например, в Adapty все продвинутые кейсы и, тем более, базовое подключение покупок с серверной валидацией уже готовы. Для работы нужно создать аккаунт и подключить Adapty SDK. Так вы сэкономите пару месяцев разработки и начнёте быстрее монетизироваться. Для роста продаж в Adapty есть продвинутая аналитика, когортный анализ, а/б тесты пейволлов и интеграции с сервисами аналитики и атрибуции.

Источник

Оцените статью