- In-App Purchase в Android приложениях
- Android in-app purchases, часть 1: конфигурация и добавление в проект
- Создание подписки/покупки
- Сравнение процесса покупки с App Store Connect
- Получение списка продуктов в приложении
- Про Adapty
- Android in-app purchases, часть 3: получение активных покупок и смена подписки
- Получение списка покупок
- Смена подписок
- Про Adapty
In-App Purchase в Android приложениях
1. Что это такое и зачем это нужно?
In-App Purchase, грубо говоря, представляет собой сервис покупки виртуальных товаров внутри приложения (например игровой валюты, новых уровней, игровых предметов и т.д.). Применяется он в основном в играх, в тех случаях, когда встает вопрос о необходимости заработка на своем творении, а распространять его платно не особо хочется (или нет смысла).
2. In-App Purchase в андроид-приложениях
Когда я столкнулся с необходимостью использования in-app purchase в разрабатываемой игре, было очень трудно найти подробную русскоязычную информацию о том, как прикрутить данный сервис к своей игре. Наиболее хорошо сервис был описан в одной из статей Хабра, но мне бы хотелось показать свое видение по данному вопросу. Поэтому решил сам написать небольшой мануал по подключению сервиса к приложению.
Для начала неплохо было бы разобраться с тем, как это все работает.
В принципе, весь процесс функционирования довольно неплохо был описан в статье, на которую я сослался выше, так что повторять заново то же самое нет особого смысла. Поэтому, я перейду сразу к делу.
Предполагается, что у вас уже есть аккаунт разработчика на Android Market.
Для того, чтобы работать с системой покупок внутри приложения нам понадобится файл с названием IMarketBillingService.aidl. Найти его можно в примере приложения по работе с in-app purchase, который в свою очередь является скачиваемым компонентом Android SDK. Запустите Android SDK and AVD Manager и выберете Available Packages. Далее необходимо будет выбрать Third party add-ons -> Google Inc. add-ons -> Google Market Billing package.
Теперь необходимо из скаченного примера скопировать в свой проект файл IMarketBillingService.aidl. Важно, чтобы он лежал в com.android.vending.billing. После этого в манифесте добавляем расширение: .
В примере приложения кроме вышеописанного файла есть также несколько реализованных классов для работы с системой платежей. Копируем их в свой проект. Что собой представляет каждый из них:
BillingReceiver – получает все асинхронные ответы с маркета и отправляет их далее в BillingService;
BillingService – отправляет запросы на маркет;
Consts – содержит все константы примера приложения;
Dungeons – обеспечивает UI и отображает историю совершенных покупок;
PurchaseDatabase – локальная база данных;
PurchaseObserver – наблюдение за изменениями, связанными с покупками;
ResponseHandler – обновление базы данных и UI;
Security – обеспечение безопасности;
Base64 и Base64DecoderException – кодирование из двоичной системы в base64. Необходимы для функционирования класса Security.
В классе Security ищем строку:
String base64EncodedPublicKey = “…”
и вписываем сюда свой PublicKey, полученный при регистрации аккаунта на Android Market.
В классе Dungeons ищем список товаров, которые предполагается продавать, и меняем их на свои. При этом, товары должны быть залиты в Android Market и опубликованы (само приложение при этом публиковать не обязательно – в случае его тестирования, однако надо не забыть добавить себя (или кого-то другого) в разработчики (делается в настройках профиля)). Далее дописываем интерфейс и в принципе приложение готово.
Однако стоит позаботиться о безопасности. Лучший способ обеспечения безопасности покупок – использование соответствующего сервиса на сервере. После того, как пользователь совершил покупку товара, маркет пришлет JSON строку с информацией о купленном товаре:
< "nonce" : 1836535032137741465,
«orders» :
< "notificationId" : "android.test.purchased",
«orderId» : «transactionId.android.test.purchased»,
«packageName» : «com.example.dungeons»,
«productId» : «android.test.purchased»,
«developerPayload» : «bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ»,
«purchaseTime» : 1290114783411,
«purchaseState» : 0 >
>
и подпись для проверки подлинности запроса.
На серверной части приложения нужно выполнить проверку вернувшейся строки с помощью подписи и вашего ключа (шифрование RSA) и вернуть результат проверки клиенту. Для этого есть небольшая открытая библиотека, написанная на php, я использовал ее.
Более подробно о безопасности при работе с in-app purchase написано тут.
Источник
Android in-app purchases, часть 1: конфигурация и добавление в проект
Всем привет, меня зовут Влад и я разработчик Android SDK для обработки платежей в мобильных приложениях в Adapty.
Внутренние покупки и в частности подписки являются наиболее популярным способом монетизировать приложение. С одной стороны, подписка дает разработчику возможность постоянно развивать контент и продукт, с другой стороны, благодаря им пользователь получает более высокое качество приложения в целом. Внутренние покупки облагаются 30% комиссией, но если пользователь подписан больше года или приложение зарабатывает меньше $1М в год, то комиссия составляет 15%.
Это первая статья из серии, посвящённой работе с внутренними покупками на Android. В этой серии мы охватываем темы от создания in-app purchases до серверной верификации платежей:
Android in-app purchases часть 1: конфигурация и добавление в проект. — Вы тут
В этой статье мы разберём, как:
создавать продукты в Google Play Console;
конфигурировать подписки: указывать длительность, стоимость, пробные периоды;
получать список продуктов в приложении.
Создание подписки/покупки
Перед тем, как мы начнем, убедитесь, что
Вы подписали все соглашения и готовы работать.
Перейдем к делу, а именно создадим наш первый продукт.
Переходим в наш аккаунт разработчика и выбираем нужное приложение.
Дальше в левом меню ищем секцию Продукты, выбираем Подписки и жмем на Создать Подписку.
Дальше попадаем в конфигуратор подписки, разберем важные вещи.
Создаем ID, который потом используем в приложении. Хорошо, когда в ID мы кодируем период подписки и какую-то еще полезную информацию. Это позволяет создавать продукты в одном стиле, чтобы в будущем было проще анализировать статистику по покупкам.
Название подписки, как пользователь ее увидит в магазине.
Описание подписки, пользователь тоже это увидит.
Скроллим ниже и выбираем период подписки, в нашем случае это неделя, и конфигурируем стоимость.
Обычно вы задаете цену в базовой валюте аккаунта, а система автоматически переводит цены в разные валюты разных стран. Но вы также можете поправить цену в конкретной стране вручную.
Обратите внимание, что Google сразу указывает налог в каждой стране, это очень круто, а в App Store Connect такого нет.
Скроллим ниже и опционально выбираем:
Бесплатный пробный период.
Начальная цена. Промо-предложение на первые периоды оплаты.
«Льготный период». То есть, если у пользователя проблема с оплатой, сколько дней вы продолжаете давать ему премиум доступ.
Возможность подписаться заново из Play Market, а не из приложения, после отмены подписки.
Сравнение процесса покупки с App Store Connect
Несмотря на то, что подписки значительно лучше монетизируются на iOS, админка Play Console выглядит намного удобнее. Она работает быстрее, лучше и проще структурирована, качественно локализована.
Сам процесс создания продукта предельно упрощен. Здесь мы описали, как создавать продукты на iOS.
Получение списка продуктов в приложении
После создания продуктов перейдем к созданию архитектуры в приложении для приема и обработки покупок. В целом процесс следующий:
Подключаем платежную библиотеку.
Разрабатываем структуру класса для взаимодействия с продуктами из Google Play.
Реализуем все методы обработки покупки.
Подключаем серверную верификацию покупки.
В этой части разберем первые два пункта.
Подключим Billing Library к проекту:
На момент написания статьи актуальной версией является 4.0.0. Вы можете в любой момент заменить ее на другую версию.
Создадим класс-обертку, который будет инкапсулировать логику взаимодействия с Google Play, и проинициализируем в нем BillingClient из библиотеки Billing Library. Назовем такой класс BillingClientWrapper.
Наш класс будет реализовывать интерфейс PurchasesUpdatedListener. Мы сразу переопределим его метод onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList
?) , который вызывается после совершения покупки, но саму реализацию опишем уже в следующей статье.
Google рекомендует, чтобы одновременно было не больше одного активного соединения BillingClient’а с Google Play, чтобы колбэк о совершенной покупке не вызывался несколько раз. Для этого целесообразно иметь единственный экземпляр BillingClient в классе-синглтоне. Класс в примере выше сам по себе синглтоном не является, но мы можем провайдить его с помощью dependency injection (например, используя Dagger или Koin) таким образом, чтобы в один момент времени существовало не больше одного экземпляра.
Для совершения любого запроса с помощью Billing Library нужно, чтобы у BillingClient’а в момент запроса было активное соединение с Google Play, но в какой-то момент оно может быть утеряно. Для удобства напишем обертку, чтобы любой запрос выполнялся только при активном соединении.
Чтобы получить продукты, нам нужно знать их идентификаторы, которые мы задавали в маркете. Но для запроса этого недостаточно, нужно указать еще и тип продуктов (подписки или разовые покупки), поэтому общий список продуктов мы можем получить только путем «склеивания» результатов двух запросов.
Так как запрос на продукты асинхронный, нам нужен колбэк, в котором мы получим либо список продуктов, либо ошибку. Billing Library при ошибке возвращает один из определенных в ней BillingResponseCode, а также debugMessage. Создадим интерфейс колбэка и модель для ошибки:
Напишем приватный метод для получения данных о продуктах конкретного типа, а также публичный метод, который «склеит» результаты от двух запросов и вернет итоговый список продуктов или ошибку.
Таким образом мы получили информацию о продуктах (SkuDetails), где есть локализованные названия, цены, тип продукта, а для подписок еще и период платежа, а также информация о начальной цене и пробном периоде (если доступно данному пользователю). Финальный класс выглядит так:
На этом все, в следующих статьях мы расскажем о реализации покупок, тестировании и обработке ошибок.
Про Adapty
Как видите, в процессе добавления покупок в приложения на Android много нюансов. Если использовать готовые библиотеки, всё будет проще. Советую познакомиться с Adapty — SDK для in-app покупок. Он не только упрощает работу по добавлению покупок:
Встроенная аналитика позволяет быстро понять основные метрики приложения.
Когортный анализ отвечает на вопрос, как быстро сходится экономика.
А/Б тесты увеличивают выручку приложения.
Интеграции с внешними системами позволяют отправлять транзакции в сервисы атрибуции и продуктовой аналитики.
Промо-кампании уменьшают отток аудитории.
Open source SDK позволяет интегрировать подписки в приложение за несколько часов.
Серверная валидация и API для работы с другими платформами.
Познакомьтесь подробнее с этими возможностями, чтобы быстрее внедрить подписки в своё приложение и улучшить конверсии.
Источник
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 для работы с другими платформами упрощает работу с покупками.
Познакомьтесь подробнее с этими возможностями, чтобы быстрее внедрить подписки в своё приложение и улучшить конверсии.
Источник