Привет, меня зовут Алексей, я разрабатываю 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

Создание покупок на 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 в месяц). Сохраните себе месяцы работы и сфокусируйтесь на главном — на вашем продукте.

Комментарии (2)


  1. qbaddev
    24.09.2021 23:50
    +1

    Все же, на kotlin более удобно все как-то скомпановано.


  1. MiT_73
    25.09.2021 09:28
    +1

    Из своего опыта, рекомендую разделить ваш монолитный пакет на платформенные пакеты (пример). Это будет удобнее для разработки и даст возможность подключить web часть вашей платформы.