Привет, меня зовут Алексей, я разрабатываю Adapty SDK для Flutter. Сегодня я расскажу про внедрение внутренних покупок в мобильное приложение на Flutter с помощью плагина, который мы разрабатываем.
Flutter — это относительно новый фреймворк от Google для быстрого создания кросс-платформенных приложений. Второй популярный фреймворк — React Native, о покупках на Реакте мы писали в другой статье.
Приложения на Flutter собираются сразу и под iOS, и под Android, следовательно, библиотека для покупок должна поддерживать и StoreKit, и Billing Library.
Архитектурно каждый плагин для платежей, включая наш Adapty Flutter SDK представляет собой обертку над нативными библиотеками StoreKit и Billing Library. В нашем случае мы делали обертку над своими же библиотеками Adapty iOS и Android SDK.
Open source библиотеки для подключения внутренних покупок на Flutter
Самые популярные решения для подключения покупок на Flutter — это open source плагины in_app_purchase и flutter_inapp_purchase. Первая — официальный плагин от команды разработчиков Flutter, второй — неофициальный.
Эти плагины созданы для клиентской части платежей, то есть они не предоставляют серверную верификацию покупок (server-side receipt validation).Вам нужно самим создать инфраструктуру со стороны сервера для валидации чеков, сбора аналитики по событиям платежей: отслеживания продлений, возвратов, триалов, отмен и т.д.
Также поддержка новых фичей, которые выкатывают сторы, появляется в этих библиотеках довольно поздно. Например, сейчас в них нет промо-офферов, pay as you go и pay upfront от iOS.
Так как наша библиотека работает с нашим сервером, то она сразу закрывает следующие технические задачи:
серверная валидация покупок;
поддержка всех актуальные нативные фичи по платежам;
DRY синтаксис;
сбор аналитики подписок и покупок;
быстрый запуск экспериментов с покупками, ценами и периодами, a/b тесты;
пейволлов и отправка скидок.
Чтобы сделать статью короче и удобнее, мы будем оставлять ссылки на предварительные шаги на нативных платформах из наших прошлых статей.
Создание покупок в iOS и в Android
Для начала надо создать аккаунт разработчика, если у вас его нет, а дальше в iOS и в Android нужно будет создать по одной недельной покупке. Как это делается, мы описали в других статьях:
После этого можем приступить к созданию и настройке проекта.
Создание проекта и настройка
Чтобы покупки заработали и у нас в системе, и в вашей реализации, нужно заполнить технические параметры.
Для iOS нужно:
указать Bundle ID — чтобы все работало;
установить App Store Server Notifications в App Store Connect — для того, чтобы мы могли узнавать о событиях подписок;
заполнить App Store Shared Secret — чтобы валидировать рецепты.
Для Android обязательны Package Name и Service Account Key File. Package name — аналог Bundle ID с iOS. Нужно указать тот, что в коде, он находится в файле /android/app/build.gradle в разделе android.defaultConfig.applicationId.
Настройка продуктов и пейволлов
Продукт в Adapty инкапсулирует в себе продукты из разных магазинов. Сейчас это App Store и Google Play, но в будущем будут другие. Сделано это для более удобной сводной статистики и для удобной работы с сущностями более высокого уровня, а не с идентификаторами.
Пейволл — это абстрактная сущность, которая содержит в себе массив продуктов и remote config (JSON c любой мета-информацией от разработчика). В приложение зашивается идентификатор пейволла и по нему приходит вся информация о продуктах и конфиг. При этом в любой момент продукты можно изменить, как и конфиг. Стандартный сценарий работы — вы делаете свою верстку пейволла, а заполняете его информацией от нас.
Создание продукта
Создадим один продукт, соответствующий недельной подписке в Google Play Console и App Store Connect. Нужно заполнить ID соответствующих продуктов из платежных систем. Важно, что так как у App Store Connect нет API, это нужно сделать руками, но только один раз.
Создание пейволла
При создании пейволла самое главное — указать его ID в удобном и понятном формате. По этому идентификатору SDK будет запрашивать всю информацию о пейволле. Нам кажется удачным такой подход: в такой архитектуре не нужно хардкодить продукты на клиенте, появляется гибкость в управлении продуктами, версионировании, запуске тестов и тд. Как альтернатива такому подходу — использовать Firebase JSON c набором вшитых продуктов, но это менее удобно и не валидирует ошибки.
Все, мы создали продукт, пейволл и теперь готовы к первой покупке. Перейдем к установке SDK.
Работа с SDK
Рассмотрим основные функции, которые нам понадобятся для работы с подписками.
Установка библиотеки
В первую очередь нужно добавить библиотеку adapty_flutter
в ваш проект. Для этого в файле pubspec.yaml
добавляем зависимость:
dependencies:
adapty_flutter: ^1.0.4
И запустите
flutter pub get
После этого вы можете импортировать Adapty SDK в свое приложение следующим образом:
import 'package:adapty_flutter/adapty_flutter.dart';
Настройка
Чтобы ваше приложение могло работать с Adapty, вам необходимо его настроить. Для этого добавьте флаг AdaptyPublicSdkKey
в Info.plist
(iOS) или в AndroidManifest.xml
(Android) с вашим публичным SDK ключом.
SDK ключ вы можете найти в настройках Adapty:
Info.plist:
<dict>
...
<key>AdaptyPublicSdkKey</key>
AndroidManifest.xml:
<application ...>
...
<meta-data
android:name="AdaptyPublicSdkKey"
android:value="PUBLIC_SDK_KEY" />
</application>
Далее активируйте SDK, вызвав на стороне Flutter следующий код, например, в методе main()
вашего проекта:
void main() {
runZoned(() async {
Adapty.activate();
final installId = await Service.getOrCreateInstallId();
await Adapty.identify(***customer-user-id***);
await Adapty.setLogLevel(AdaptyLogLevel.verbose);
runApp(MyApp());
});
}
Функция void Adapty.activate()
активирует библиотеку Adapty_Flutter
:
Future<bool> Adapty.identify(String customerUserId)
Adapty.identify
позволяет установить id пользователя. Adapty отправляет его в подписку и аналитику, чтобы присвоить события нужному профилю. Вы также сможете найти клиентов по customerUserId в Profiles.
Adapty регистрирует ошибки и другую важную информацию, чтобы помочь вам понять, что происходит. Функция
Future<void> Adapty.identify(AdaptyLogLevel value)
позволяет установить один из 3 возможных значений:
AdaptyLogLevel.none
(по умолчанию): ничего не будет регистрироваться;AdaptyLogLevel.errors
: будут регистрироваться только ошибки;AdaptyLogLevel.verbose
: будут регистрироваться вызовы методов, запросы API/ответы и ошибки.
Получение пейволлов
Чтобы получить список пейволов, выполните следующий код:
try {
final GetPaywallsResult getPaywallsResult = await Adapty.getPaywalls(forceUpdate: Bool);
final List<AdaptyPaywall> paywalls = getPaywallsResult.paywalls;
} on AdaptyError(adaptyError) {}
catch(e) {}
Функция Adapty.getPaywalls()
возвращает объект GetPaywallsResult
который содержит:
пейволлы: массив пейволлов (
AdaptyPaywall
). Модель содержит список продуктов, ID пейволла,custom payload
и несколько других значений.
Совершение покупок
После того, как на предыдущем шаге мы получили массив пейволов, требуется найти нужный нам, для того чтобы отобразить соответствующие продукты в интерфейсе (предположим, что наш пейвол в админке был назван your_paywall_id):
final List<AdaptyPaywall>? paywalls = getPaywallsResult.paywalls;
myPaywall = paywalls?.firstWhere((paywall) => paywall.developerId == "your_paywall_id", orElse: null);
Далее, воспользовавшись массивом продуктов из поля products, отображаем их все. Допустим, пользователь желает купить первый продукт, так что для простоты возьмем первый элемент массива продуктов:
final AdaptyProduct? product = myPaywall?.products?.first;
Для запуска процесса покупки вызываем функцию makePurchaseResult
(не забыв обернуть в try-catch блок, чтобы получать все ошибки от sdk)
final MakePurchaseResult makePurchaseResult = await Adapty.makePurchase(product);
После того, как функция отработает успешно, в переменной makePurchaseResult будет результат. Проверяем наличие уровня доступа после завершения процесса покупки:
final isPremium = makePurchaseResult?.purchaserInfo?.accessLevels['premium']?.isActive ?? false;
примечание: AdaptyErrorCode.paymentCancelled
указывает на то, что пользователь сам отменил покупку, и по сути не является ошибкой.
Для восстановления покупок используется метод .restorePurchases()
:
try {
final RestorePurchasesResult restorePurchasesResult = await Adapty.restorePurchases();
// "premium" is an identifier of default access level
if (restorePurchasesResult?.purchaserInfo?.accessLevels['premium']?.isActive ?? false) {
// grant access to premium features
}
} on AdaptyError catch (adaptyError) {}
catch (e) {}
Обратите внимание, что и объект MakePurchaseResult
, и объект RestorePurchasesResult
включают purchaserInfo
. Этот объект содержит информацию об уровнях доступа, подписках и покупках без подписки. Как правило, вы должны проверить только статус уровня доступа, чтобы определить, имеет ли пользователь премиум-доступ к приложению.
Статус подписки
Чтобы не проверять предыдущую цепочку транзакций, мы оперируем понятием уровня доступа. Уровень доступа — эта флаг, который говорит о том, какого уровня функционал доступен пользователю в приложении. Если покупок не было, то уровень доступа пустой. Иначе, тот, который вы привязали к продукту.
Например, у вас может быть две уровня доступа: серебряный и золотой. Разные покупки разблокируют разный доступ и фичи. Большинство приложений имеют один уровень доступа.
Достаточно проверить, что у пользователя есть активный уровень доступа, чтобы понять, что подписка пользователя активна. Для этого используется метод .getPurchaserInfo()
try {
AdaptyPurchaserInfo purchaserInfo = await Adapty.getPurchaserInfo();
// "premium" is an identifier of default access level
if (purchaserInfo.accessLevels['premium']?.isActive ?? false) {
// grant access to premium features
}
} on AdaptyError catch (adaptyError) {}
catch (e) {}
Также вы можете узнавать об изменение статуса уровня доступа подписчика, подписавшись на стрим .purchaserInfoUpdateStream
, как показано ниже:
Adapty.purchaserInfoUpdateStream.listen((purchaserInfo) {
print('#Adapty# Purchaser Info Updated');
if (purchaserInfo.accessLevels['premium'].isActive) {
// grant access to premium features
}
});
Заключение
Мы сделали SDK так, чтобы вы могли максимально быстро и гибко внедрить платежи к себе в приложение. Более того, мы постарались сделать дальнейшие шаги работы с платежами, такие как А/Б тесты, аналитика и интеграции максимально простыми и быстрыми.
Мы делаем полный функционал покупок бесплатно (если ваша выручка не превышает $10 000 в месяц). Сохраните себе месяцы работы и сфокусируйтесь на главном — на вашем продукте.
qbaddev
Все же, на kotlin более удобно все как-то скомпановано.